/*
 * $Workfile: MapViewer.js $
 * 
 * $Logfile: /CheshireCCSolutions/RoadworksSolution/CheshireCC.Roadworks.Web.Internet/Scripts/MapViewer.js $
 * 
 * $Revision: 99 $
 * 
 * $Date: 01/02/07 11:09 $
 * 
 * Original Author: Andrew Montgomery
 * 
 * $Author: Andrewm $
 * 
 * Copyright(c) Cheshire County Council All Rights Reserved.
 * 
 */

// This script uses Object-Oriented JavaScript.
// Please ensure you are familiar with the concepts of OOJ
// before attempting to make changes.

// Coordinates object #1
function CoordsMap(_e,_n) {
	this.e = 0;
	this.n = 0;
	if (arguments.length == 2) {
		this.e = _e;
		this.n = _n;
	}
}
// Coordinates object #2
function CoordsScreen(_x,_y) {
	this.x = 0;
	this.y = 0;
	if (arguments.length == 2) {
		this.x = _x;
		this.y = _y;
	}
}
// Coordinates object #3
function ImageSize(_w,_h) {
	this.w = 0;
	this.h = 0;
	if (arguments.length == 2) {
		this.w = _w;
		this.h = _h;
	}
}

// MouseObject is used for all methods surrounding mouse positioning,
// including drag-n-move and identify.
function MouseObject() {
	this.startPoint = new CoordsScreen(0,0);
}
MouseObject.prototype.start = function(eventObject) {
	this.startPoint.x = eventObject.clientX;
	this.startPoint.y = eventObject.clientY;
}
MouseObject.prototype.getShift = function(eventObject) {
	var pt = new CoordsScreen(0,0);
	pt.x = eventObject.clientX - this.startPoint.x;
	pt.y = eventObject.clientY - this.startPoint.y;
	return pt;
}
MouseObject.prototype.getOffset = function(eventObject) {
	var pt = new CoordsScreen(0,0);
	if (eventObject.offsetX) {
		pt.x = eventObject.offsetX;
		pt.y = eventObject.offsetY;
	} else {
		pt.x = eventObject.layerX;
		pt.y = eventObject.layerY;
	}
	return pt;
}
// START method for finding offset relative to element of mouse event
/**
  * Retrieve the coordinates of the given event relative to the center
  * of the widget.
  *
  * @param event
  *  A mouse-related DOM event.
  * @param reference
  *  A DOM element whose position we want to transform the mouse coordinates to.
  * @return
  *    A hash containing keys 'x' and 'y'.
  */
MouseObject.prototype.getRelativeCoordinates = function(event, reference) {
	var x, y;
	event = event || window.event;
	var el = event.target || event.srcElement;
	if (!window.opera && typeof event.offsetX != 'undefined') {
		// Use offset coordinates and find common offsetParent
		var pos = { x: event.offsetX, y: event.offsetY };
		// Send the coordinates upwards through the offsetParent chain.
		var e = el;
		while (e) {
			e.mouseX = pos.x;
			e.mouseY = pos.y;
			pos.x += e.offsetLeft;
			pos.y += e.offsetTop;
			e = e.offsetParent;
		}
		// Look for the coordinates starting from the reference element.
		var e = reference;
		var offset = { x: 0, y: 0 }
		while (e) {
			if (typeof e.mouseX != 'undefined') {
				x = e.mouseX - offset.x;
				y = e.mouseY - offset.y;
				break;
			}
			offset.x += e.offsetLeft;
			offset.y += e.offsetTop;
			e = e.offsetParent;
		}
		// Reset stored coordinates
		e = el;
		while (e) {
			e.mouseX = undefined;
			e.mouseY = undefined;
			e = e.offsetParent;
		}
	} else {
		// Use absolute coordinates
		var pos = this.getAbsolutePosition(reference);
		x = event.pageX - pos.x;
		y = event.pageY - pos.y;
	}
	// Subtract distance to middle
	return { x: x, y: y };
}
MouseObject.prototype.getAbsolutePosition = function(element) {
	// part of getRelativeCoordinates above; not to be used in isolation (because it doesn't work in IE)
	var r = { x: element.offsetLeft, y: element.offsetTop };
	if (element.offsetParent) {
		var tmp = this.getAbsolutePosition(element.offsetParent);
		r.x += tmp.x;
		r.y += tmp.y;
	}
	return r;
}
// END method for finding offset relative to element of mouse event


// The following functions are event-handlers for the mouse on the map
var bMoving = false;
function mapMouseDown(evt) {
	var e = (window.event) ? window.event : evt;
	if (bMoving) return false;
	bMoving = true;
	Mouse.start(e);
	Map.divPrev.childNodes[0].style.width  = Map.width;
	Map.divPrev.childNodes[0].style.height = Map.height;
	Map.mapWrap.style.cursor = Map.cursorGrabbing; // grabbing cursor

	// This block allows the user to drag the map, release, then quickly do a new drag before the new image has loaded
	if (true) {
		var currX = Map.divCurr.childNodes[0].style.marginLeft;
		var currY = Map.divCurr.childNodes[0].style.marginTop;
		if (currX != "") currX = parseInt(currX); else currX = 0;
		if (currY != "") currY = parseInt(currY); else currY = 0;
		Mouse.startPoint.x -= currX;
		Mouse.startPoint.y -= currY;
	}
	return false; // this stops FireFox from selecting text or images
}
function mapMouseMove(evt) {
	var e = (window.event) ? window.event : evt;
	if (!bMoving) return false;
	var pt = Mouse.getShift(e);
	Map.divOlde.childNodes[0].style.marginLeft = pt.x + "px";
	Map.divCurr.childNodes[0].style.marginLeft = pt.x + "px";
	Map.divPrev.childNodes[0].style.marginLeft = pt.x + "px";
	Map.divOlde.childNodes[0].style.marginTop  = pt.y + "px";
	Map.divCurr.childNodes[0].style.marginTop  = pt.y + "px";
	Map.divPrev.childNodes[0].style.marginTop  = pt.y + "px";
	Identify.drift(pt);
	return false;
}
function mapMouseUp(evt) {
	var e = (window.event) ? window.event : evt;
	if (!bMoving) return false;
	bMoving = false;
	var shiftBy = Mouse.getShift(e);
	Identify.shift(shiftBy);
	Map.mapWrap.style.cursor = Map.cursorGrab; // grabbing cursor
	if (shiftBy.x == 0 && shiftBy.y == 0) {
		// identify mode
		var pnt = Mouse.getRelativeCoordinates(e, Map.divCurr);
		Identify.showLoading(pnt);
	} else {
		// map move mode
		// apply screen shift to map location, by multiplying by scale
		Map.e -= shiftBy.x * Map.scale;
		Map.n += shiftBy.y * Map.scale;
		// then load new image
		Map.refresh();
	}
	return false;
}
function mapMouseOut(evt) {
	return mapMouseUp(evt);
}


// A map requires 5 or 6 key parameters:
//   Either an Envelope: minX, minY, maxX, maxY,   width, height
//   or  Center + Scale: centerE, centerN, scale,  width, height

// EnvelopeObject is exactly what it sounds like
function EnvelopeObject(_minx, _miny, _maxx, _maxy) {
	if (arguments.length != 4) {
		this.minx = -1;
		this.miny = -1;
		this.maxx = -1;
		this.maxy = -1;
	} else {
		this.minx = _minx;
		this.miny = _miny;
		this.maxx = _maxx;
		this.maxy = _maxy;
	}
}
// Convert an EnvelopeObject to a CenterAndScale object
EnvelopeObject.prototype.ToCAS = function(width, height) {
	var cas = new CenterAndScale();
	cas.e = (this.minx + this.maxx) / 2;
	cas.n = (this.miny + this.maxy) / 2;
	cas.scale = Math.max((this.maxx - this.minx) / width, (this.maxy - this.miny) / height);
	return cas;
}
// CenterAndScale is exactly what is says on the tin
function CenterAndScale() {
	this.e = -1;
	this.n = -1;
	this.scale = -1;
}


// Three special functions for window.setTimeout (which can't call object methods)
function Map_slideAlong()     { Map.slideAlong();    }
function Map_waitForResize()  { Map.waitForResize(); }
function Map_setMapSize()     { Map.setMapSize();    }

// OverviewObject controls the overview map, the red box, 
// and clicking on the overview map.
function OverviewObject() {
	this.div = document.getElementById("divOverview");
	this.red = document.getElementById("divRedBox"); // red box element
	this.ovScale = 700;
	this.minE = 316000;
	this.maxN = 402000;

	this.div.onmousedown = overviewMouseDown;
	this.div.onmousemove = overviewMouseMove;
	this.div.onmouseup   = overviewMouseUp;
	this.div.onmouseout  = overviewMouseOut;
}
OverviewObject.prototype.setDimensions = function(w,h) {
	this.div.style.marginLeft = (w - 147) + "px";
	this.div.style.marginTop  = (h - 107) + "px";
}
OverviewObject.prototype.updateOverview = function() {
	var ovCenterX = (Map.e - this.minE) / this.ovScale;
	var ovCenterY = (this.maxN - Map.n) / this.ovScale;
	var ovWidth  = (Map.scale * Map.width ) / this.ovScale;
	var ovHeight = (Map.scale * Map.height) / this.ovScale;
	if (ovWidth  < 5) ovWidth  = 5;
	if (ovHeight < 5) ovHeight = 5;
	var ovLeft = Math.round(ovCenterX - (ovWidth  / 2));
	var ovTop  = Math.round(ovCenterY - (ovHeight / 2));
	this.red.style.marginLeft = ovLeft + "px";
	this.red.style.marginTop  = ovTop  + "px";
	
	this.red.style.width  = ovWidth  + "px";
	this.red.style.height = ovHeight + "px";
	this.red.childNodes[0].style.height = Math.round((Map.scale * Map.height) / this.ovScale) + "px";
	return;
}
OverviewObject.prototype.ClickMe = function(evt) {
	var e = (window.event) ? window.event : evt;
	var pnt = Mouse.getRelativeCoordinates(e, this.div);
	pnt.x -= 7; // adjust 7px for the border around the overview map
	pnt.y -= 7; // adjust 7px for the border around the overview map
	var newE = (pnt.x * this.ovScale) + this.minE;
	var newN = this.maxN - (pnt.y * this.ovScale);
	var oldExtent = Map.getExtent();
	Map.e = newE;
	Map.n = newN;
	Identify.adjustForScale(Map.scale, oldExtent);
	Map.clearImage();
	Map.refresh();
}
// Event-handlers for the Overview object
function overviewMouseDown(evt) {
	var e = (window.event) ? window.event : evt;
	if (e.stopPropagation)
		e.stopPropagation();
	else
		e.cancelBubble = true;
	return false;
}
function overviewMouseMove() {
	return false;
}
function overviewMouseUp() {
	return false;
}
function overviewMouseOut() {
	return false;
}


// MapObject is the main map object; it does most of the work in this application
function MapObject() {
	// initial constants
	this.initE = 363000;
	this.initN = 363000;
	this.initScale = 160; // equivalent to 1:600'000
	
	// initialise member variables
	this.e = this.initE;
	this.n = this.initN;
	this.scale = this.initScale;
	this.width = 280;
	this.height = 200;
	this.datespan = "1";

	this.gLastImageSize = new ImageSize();
	this.bLoading = true; // freeze all buttons from start
	this.smoothZoomDirection = 0;
	this.smoothZoomFactor = 1;
	this.mapWrap = null;
	this.divPrev = null;
	this.divCurr = null;
	this.divOlde = null;
	this.divOver = null;
	this.divInfo = null;
	this.dth = document.getElementById("divTextHeight");
	this.dth16 = this.dth.childNodes[0];
	if (this.dth16.nodeName.toUpperCase() != "DIV")
		this.dth16 = this.dth.childNodes[1];

	this.gSlideDirection = "";
	this.gSlideOffset = 0;

	this.resizeWaitId = false;

	// cursor
	this.cursorGrab     = navigator.userAgent.indexOf("Gecko") == -1 ? "images/grab.cur"     : "-moz-grab";
	this.cursorGrabbing = navigator.userAgent.indexOf("Gecko") == -1 ? "images/grabbing.cur" : "-moz-grabbing";
}
MapObject.prototype.reset = function() {
	Identify.hide(); // hide the identify box; if this line is removed, don't forget to re-position the identify box to match the new extents
	ZoomMarker.moveToNewLocation(this.initE, this.initN, this.initScale);
}
MapObject.prototype.getExtent = function() {
	var env = new EnvelopeObject();
	env.minx = this.e - ((this.width  * this.scale) / 2);
	env.maxx = this.e + ((this.width  * this.scale) / 2);
	env.miny = this.n - ((this.height * this.scale) / 2);
	env.maxy = this.n + ((this.height * this.scale) / 2);
	return env;
}
MapObject.prototype.clearImage = function() {
	this.divCurr.childNodes[0].src = "images/transparent.gif";
}
MapObject.prototype.refresh = function() {
	this.swapPrevCurrNext();
	this.divCurr.childNodes[0].style.marginTop  = "0px";
	this.divCurr.childNodes[0].style.marginLeft = "0px";
	this.gLastImageSize.w = this.width;
	this.gLastImageSize.h = this.height;
	this.divCurr.childNodes[0].src = "MapImageRoadworks.ashx?w=" + this.width + "&h=" + this.height + "&e=" + this.e + "&n=" + this.n + "&scale=" + this.scale + "&datespan=" + this.datespan;
	Overview.updateOverview();
	Identify.updateIdentify();
	clearDropDown();
}
MapObject.prototype.loadStepOne = function() {
	this.mapWrap = document.getElementById("divMapContainer");
	this.divCurr = document.getElementById("divMap1");
	this.divPrev = document.getElementById("divMap2");
	this.divOlde = document.getElementById("divMap3");
	this.divInfo = document.getElementById("divInfo");
	
	this.e = parseFloat(document.getElementById("hidInitialEasting").value);
	this.n = parseFloat(document.getElementById("hidInitialNorthing").value);
	this.scale = parseFloat(document.getElementById("hidInitialScale").value);

	this.mapWrap.onmousedown = mapMouseDown;
	this.mapWrap.onmousemove = mapMouseMove;
	this.mapWrap.onmouseup   = mapMouseUp;
	this.mapWrap.onmouseout  = mapMouseOut;

	// Set the window resize event
	window.onresize = Map_waitForResize; // works in FFX and IE 
	//document.body.onresize = Map_waitForResize; // only works in IE

	// Table4 is a stupid table in the template which we can't change;
	// fundamentally it causes problems because it insists on forcing
	// the page height to 600px, which on a 1024x768 monitor will always 
	// result in ugly scroll bars.
	var tbl4 = document.getElementById("Table4");
	if (tbl4 != null) {
		if (typeof(tbl4.height) != "undefined") 
			tbl4.height = ""; // for IE
		else
			tbl4.style.height = "auto"; // for Firefox
		tbl4.deleteRow(0);
		tbl4.deleteRow(0);
	}
}
MapObject.prototype.loadStepTwo = function() {
	this.scale = ZoomMarker.cleanDirtyScale(this.scale);
	this.setMapSize();
	this.bLoading = false;
}
MapObject.prototype.setMapSize = function() {
	if (!this.setDimensions()) return;
	this.refresh();
}
MapObject.prototype.setDimensions = function() {
	var oldW = Map.width;
	var oldH = Map.height;
	var windowW, windowH;
	var adjustW, adjustH;
	if (self.innerWidth)
	{
		// FireFox
		windowW = self.innerWidth;
		windowH = self.innerHeight;
		adjustW = 432;
		adjustH = 109;
		var testScrollBar = document.documentElement.scrollWidth;
		if (testScrollBar != windowW)
			adjustW += 16;
	}
	else if (document.documentElement && document.documentElement.clientWidth)
	{
		// IE6+ (standards mode)
		windowW = document.documentElement.clientWidth;
		windowH = document.documentElement.clientHeight;
		adjustW = 500;
		adjustH = 152;
	}
	else if (document.body)
	{
		// IE5.5-, IE6 (quirks mode)
		windowW = document.body.clientWidth;
		windowH = document.body.clientHeight;
		adjustW = 480;
		adjustH = 152;
	} 
	else 
	{
		// unknown
		alert("This map may not work in your browser");
		windowW = -1;
		windowH = -1;
		adjustW = 0;
		adjustH = 0;
	}
	this.width  = windowW - adjustW; // adjust for table
	this.height = windowH - adjustH; // adjust for table
	var wMin = 200, hMin = 200; // minimums
	var wMax = 800, hMax = 800; // maximums
	if (this.width  < wMin)  this.width  = wMin;
	if (this.height < hMin)  this.height = hMin;
	if (this.width  > wMax)  this.width  = wMax;
	if (this.height > hMax)  this.height = hMax;
	if (oldW == this.width && oldH == this.height)
	{
		if (this.gLastImageSize.w==oldW && this.gLastImageSize.h==oldH)
			return false;
		else
			return true;
	} 
	else 
	{
		this.divPrev.style.width   = this.width  + "px";
		this.divPrev.style.height  = this.height + "px";
		this.divCurr.style.width   = this.width  + "px";
		this.divCurr.style.height  = this.height + "px";
		this.divOlde.style.width   = this.width  + "px";
		this.divOlde.style.height  = this.height + "px";
		this.mapWrap.style.width   = this.width  + "px";
		this.mapWrap.style.height  = this.height + "px";
		Overview.setDimensions(this.width,this.height);
		var dthHeight = this.height-50;
		this.dth.style.height = dthHeight + "px";// 300 by default
		this.dth16.style.height = (dthHeight+16) + "px"; // always +16 to hide horizontal scrollbar
		return true;
	}
}
MapObject.prototype.panClick = function(dir) {
	if (this.bLoading) return false;
	this.bLoading = true;
	var midOffset = 0;
	var oldExtent = Map.getExtent();
	for (var i=0; i<dir.length; i++)
	{
		var dir2 = dir.substr(i,1);
		switch (dir2) {
			case "n": this.n += Math.round((this.height * this.scale) / 2); midOffset = Math.round(this.height / 2); break;
			case "s": this.n -= Math.round((this.height * this.scale) / 2); midOffset = Math.round(this.height / 2); break;
			case "e": this.e += Math.round((this.width  * this.scale) / 2); midOffset = Math.round(this.width  / 2); break;
			case "w": this.e -= Math.round((this.width  * this.scale) / 2); midOffset = Math.round(this.width  / 2); break;
		}
	}
	Identify.adjustForScale(this.scale, oldExtent);
	this.gSlideDirection = dir;
	this.refresh();
	for (var i=0; i<dir.length; i++)
	{
		var dir2 = dir.substr(i,1);
		switch (dir2) {
			case "n": this.divCurr.childNodes[0].style.marginTop  = "-" + midOffset + "px"; break;
			case "s": this.divCurr.childNodes[0].style.marginTop  = midOffset + "px"; break;
			case "e": this.divCurr.childNodes[0].style.marginLeft = midOffset + "px"; break;
			case "w": this.divCurr.childNodes[0].style.marginLeft = "-" + midOffset + "px"; break;
		}
	}
	window.setTimeout(Map_slideAlong,10);
}
MapObject.prototype.slideAlong = function() {
	var midOffset;
	for (var i=0; i<this.gSlideDirection.length; i++)
	{
		var dir2 = this.gSlideDirection.substr(i,1);
		switch (dir2) {
			case "n":
			case "s": midOffset = Math.round(Map.height/2); break;
			case "e":
			case "w": midOffset = Math.round(Map.width/2); break;
		}
	}
	if (this.gSlideOffset >= midOffset) {
		this.gSlideOffset = 0;
		this.bLoading = false;
	} else {
		this.gSlideOffset += 10;
		if (this.gSlideOffset > midOffset)  this.gSlideOffset = midOffset; // limit to maximum movement (half-map)
		for (var i=0; i<this.gSlideDirection.length; i++)
		{
			var dir2 = this.gSlideDirection.substr(i,1);
			switch (dir2) {
				case "n": 
					this.divPrev.childNodes[0].style.marginTop = this.gSlideOffset + "px";
					this.divCurr.childNodes[0].style.marginTop = "-" + (midOffset - this.gSlideOffset) + "px";
					break;
				case "s": 
					this.divPrev.childNodes[0].style.marginTop = "-" + this.gSlideOffset + "px";
					this.divCurr.childNodes[0].style.marginTop = (midOffset - this.gSlideOffset) + "px";
					break;
				case "e":
					this.divPrev.childNodes[0].style.marginLeft = "-" + this.gSlideOffset + "px";
					this.divCurr.childNodes[0].style.marginLeft = (midOffset - this.gSlideOffset) + "px";
					break;
				case "w":
					this.divPrev.childNodes[0].style.marginLeft = this.gSlideOffset + "px";
					this.divCurr.childNodes[0].style.marginLeft = "-" + (midOffset - this.gSlideOffset) + "px";
					break;
			}
		}
		window.setTimeout(Map_slideAlong,10);
	}
}
MapObject.prototype.swapPrevCurrNext = function() {
	var tmp = this.divPrev;
	this.divPrev = this.divCurr;
	this.divCurr = this.divOlde;
	this.divOlde = tmp;
	tmp = null;
	this.divCurr.style.zIndex = 4;
	this.divPrev.style.zIndex = 3;
	this.divOlde.style.zIndex = 2;
	this.divOlde.childNodes[0].src = "images/transparent.gif"; //"images/loading.gif";
	this.divOlde.style.visibility = "hidden";
	this.divOlde.childNodes[0].style.marginLeft = "";
	this.divOlde.childNodes[0].style.marginTop = "";
	this.divOlde.childNodes[0].style.width  = this.width;
	this.divOlde.childNodes[0].style.height = this.height;

	this.divCurr.style.visibility = "";
	this.divCurr.childNodes[0].style.width  = this.divCurr.style.width;
	this.divCurr.childNodes[0].style.height = this.divCurr.style.height;
}
MapObject.prototype.waitForResize = function() {
	if (this.resizeWaitId !== false)
		window.clearTimeout(this.resizeWaitId);
	this.setDimensions();
	this.resizeWaitId = window.setTimeout(Map_setMapSize,500);
}
function map_SmoothZoomInStep(starting1, starting2) {
	if (arguments.length == 2)  { // starting
		Map.smoothZoomDirection = 1;
		Map.smoothZoomFactor = 1;
	}
	Map.smoothZoomFactor += 0.1; // increase from 1.0 to 2.0 in steps of 0.1
	Map.divPrev.childNodes[0].style.marginLeft = 0 - (Map.width * (Map.smoothZoomFactor - 1)) / 2;
	Map.divPrev.childNodes[0].style.marginTop = 0 - (Map.height * (Map.smoothZoomFactor - 1)) / 2;
	//Map.divPrev.childNodes[0].style.zoom = Map.smoothZoomFactor;
	Map.divPrev.childNodes[0].style.width  = Map.width * Map.smoothZoomFactor;
	Map.divPrev.childNodes[0].style.height = Map.height * Map.smoothZoomFactor;
	if (Map.smoothZoomFactor < 2 && Map.smoothZoomDirection == 1) {
		window.setTimeout(map_SmoothZoomInStep,15);
	} else {
		Map.smoothZoomDirection = 0;
		Map.smoothZoomFactor = 1;
	}
}
function map_SmoothZoomOutStep(starting1, starting2) {
	if (arguments.length == 2)  { // starting
		Map.smoothZoomDirection = -1;
		Map.smoothZoomFactor = 1;
	}
	Map.smoothZoomFactor -= 0.05;
	Map.divPrev.childNodes[0].style.marginLeft = (Map.width * (1-Map.smoothZoomFactor)) / 2;
	Map.divPrev.childNodes[0].style.marginTop = (Map.height * (1-Map.smoothZoomFactor)) / 2;
	//Map.divPrev.childNodes[0].style.zoom = Map.smoothZoomFactor;
	Map.divPrev.childNodes[0].style.width  = Map.width  * Map.smoothZoomFactor;
	Map.divPrev.childNodes[0].style.height = Map.height * Map.smoothZoomFactor;
	if (Map.smoothZoomFactor > 0.5 && Map.smoothZoomDirection == -1) {
		window.setTimeout(map_SmoothZoomOutStep,15);
	} else {
		Map.smoothZoomDirection = 0;
		Map.smoothZoomFactor = 1;
	}
}



// ZoomMarkerObject is used to control the zoom markers / sliders; 
// it is also used to zoom the map.
// Any changes to the scale should be done via this object, not via tha Map object;
// this object will adjust the Map object as required.
function ZoomMarkerObject() {
	this.notchIndex = 0;
	this.previousNotchIndex = 0;
	this.clickedNotchIndex = 0;
	this.moving = false;
	this.divMarker = document.getElementById("divMarker");
	if (typeof(this.divMarker.firstChild.style.filter) != "undefined") {
		this.divMarker.firstChild.src = "images/zoom/transparent.png";
	}
	this.divZoomBarWhole = document.getElementById("divZoomBarWhole");
	if (typeof(this.divZoomBarWhole.firstChild.style.filter) != "undefined") {
		this.divZoomBarWhole.firstChild.src = "images/zoom/transparent.png";
	}
	this.notchHeight = 8; // each notch is 8px tall
	this.scales = new Array(160,80,50,25,10,5,2,1,0.635,0.5);
	if (Map.scale != this.scales[0]) 
	this.initialise(Map.scale);
}
// Private method
ZoomMarkerObject.prototype.zoomToCurrentNotch = function(bClearMap) {
	// bClearMap decides whether to clear the current image;
	// basically this should be true for all zooms except the smooth zooms
	var oldExtent = Map.getExtent();
	var oldScale = Map.scale;
	Map.scale = this.scales[this.notchIndex];
	Identify.adjustForScale(oldScale, oldExtent);
	if (bClearMap) {
		Map.clearImage();
	}
	Map.refresh();
}	
// Private method
ZoomMarkerObject.prototype.moveNotchToIndex = function(newIndex) {
	this.divMarker.style.marginTop = (156 - (this.notchHeight * newIndex)) + "px";
	this.notchIndex = newIndex;
}
// Public method
ZoomMarkerObject.prototype.initialise = function(newScale) {
	// find the index of this new scale, erring on the side of higher scales; e.g. mNTS(60) will move to 100
	var i;
	for (i=0; i<this.scales.length; i++)
	{
		if (newScale >= this.scales[i]) break;
	}
	if (i == this.scales.length) i--;
	this.moveNotchToIndex(i);
}
// Public method
ZoomMarkerObject.prototype.mClick = function(ix) {
	if (ix != this.notchIndex) {
		this.moveNotchToIndex(ix);
		this.zoomToCurrentNotch(true);
	}
	return false;
}
// Public method
ZoomMarkerObject.prototype.mDown = function(evt,ix) {
	var e = (window.event) ? window.event : evt;
	this.previousNotchIndex = this.notchIndex;
	if (ix != this.notchIndex) {
		this.moveNotchToIndex(ix);
	}
	this.clickedNotchIndex = this.notchIndex;
	this.moving = true;
	Mouse.start(e);
	return false;
}
// Public method
ZoomMarkerObject.prototype.mMove = function(evt) {
	if (!this.moving) return false;
	var e = (window.event) ? window.event : evt;
	var shift = Mouse.getShift(e);
	var newIndex = this.clickedNotchIndex + Math.round(shift.x/this.notchWidth);
	if (newIndex < 0) newIndex = 0;
	if (newIndex >= this.scales.length)  newIndex = this.scales.length-1;
	if (newIndex != this.notchIndex) {
		this.moveNotchToIndex(newIndex);
	}
	return false;
}
// Public method
ZoomMarkerObject.prototype.mUp = function(evt) {
	if (!this.moving) return false;
	this.mMove(evt); // one last move
	var e = (window.event) ? window.event : evt;
	this.moving = false;
	if (this.notchIndex != this.previousNotchIndex) {
		this.zoomToCurrentNotch(true);
	}
	return false;
}
// Public method
ZoomMarkerObject.prototype.mOut = function(evt) {
	if (!this.moving) return false;
	var e = (window.event) ? window.event : evt;
	var ToRelatedTarget = e.toElement;
	var nn = "" + ToRelatedTarget.nodeName; // make sure it's a string
	if (nn.toUpperCase() != "IMG") {
		this.moving = false;
		this.moveNotchToIndex( this.previousNotchIndex ); // revert to original
	}
	return false;
}
// Public method
ZoomMarkerObject.prototype.zoomIn = function() {
	if (this.notchIndex == this.scales.length-1) {
		alert("You can't zoom in any further");
	} else {
		this.moveNotchToIndex(this.notchIndex+1);
		this.zoomToCurrentNotch(false);
		map_SmoothZoomInStep(true, true);
	}
	return false;
}
// Public method
ZoomMarkerObject.prototype.zoomOut = function() {
	if (this.notchIndex == 0) {
		alert("You can't zoom out any further");
	} else {
		this.moveNotchToIndex(this.notchIndex-1);
		this.zoomToCurrentNotch(false);
		map_SmoothZoomOutStep(true, true);
	}
	return false;
}
// Public method
ZoomMarkerObject.prototype.cleanDirtyScale = function(newScale) {
	// find the index of this new scale, erring on the side of higher scales; e.g. mNTS(60) will move to 100
	var i;
	for (i=this.scales.length-1; i>0; i--)
	{
		if (newScale <= this.scales[i]) return this.scales[i];
	}
	return this.scales[i];
}
// Public method
ZoomMarkerObject.prototype.zoomToDirtyScale = function(newScale) {
	// find the index of this new scale, erring on the side of higher scales; e.g. mNTS(60) will move to 100
	var i;
	for (i=0; i<this.scales.length; i++)
	{
		if (newScale >= this.scales[i]) break;
	}
	if (i == this.scales.length) i--;
	this.moveNotchToIndex(i);
	this.zoomToCurrentNotch(true);
}
// Public method
ZoomMarkerObject.prototype.moveToNewLocation = function(newE, newN, newScale) {
	// find the index of this new scale, erring on the side of higher scales; e.g. mNTS(60) will move to 100
	var i;
	for (i=0; i<this.scales.length-1; i++)
	{
		if (newScale >= this.scales[i]) break;
	}
	Map.e = newE;
	Map.n = newN;
	this.moveNotchToIndex(i);
	this.zoomToCurrentNotch(true);
}
// END OF ZoomMarkerObject




// ================== IDENTIFY STUFF =============

function IdentifyObject() {
	this.infoWin = document.getElementById("divInfo");
	this.nodes = 
		this.infoWin.childNodes[0].nodeName.toUpperCase() == "DIV" ? 
		this.infoWin.childNodes[0].getElementsByTagName("div") : // IE
		this.infoWin.childNodes[1].getElementsByTagName("div"); // FFX
	this.buttons = this.infoWin.getElementsByTagName("button");
	// identify-related variables
	this.arrIdentifyCoords = new Array(0);
	this.arrIdentifyColors = new Array(0);
	this.arrIdentifyTexts  = new Array(0);
	this.point = new CoordsScreen(0,0);
	this.loading = false;
	this.prevNextContent = new Array();
	this.currentPrevNextIndex = 0;
	//this.identifyShift = null;
}
IdentifyObject.prototype.show = function() {
	this.infoWin.style.display = "";
}
IdentifyObject.prototype.hide = function() {
	this.infoWin.style.display = "none";
}
IdentifyObject.prototype.adjustForScale = function(oldScale, oldExtent) {
	// Translate screen point to map coords under the old scale and extent
	var newExtent = Map.getExtent();
	var mapPoint = new CoordsMap();
	mapPoint.e = (this.point.x * oldScale) + oldExtent.minx;
	mapPoint.n = ((Map.height - this.point.y) * oldScale) + oldExtent.miny;
	// Translate map coords to new screen point under the new scale and extent
	this.point.x = (mapPoint.e - newExtent.minx) / Map.scale;
	this.point.y = Map.height - ((mapPoint.n - newExtent.miny) / Map.scale);
	this.setPosition(null);
	if (this.point.x > Map.width || this.point.x < 0 || this.point.y > Map.height || this.point.y < 0)
	{
		this.hide();
	}
}
IdentifyObject.prototype.showOnMapHI = function(id, minx, miny, maxx, maxy, title, subtitle, dates, org, shortDesc ) {
	var env = new EnvelopeObject(minx, miny, maxx, maxy);
	var cas = env.ToCAS(Map.width,Map.height);
	cas.e -= cas.scale * 120;
	ZoomMarker.moveToNewLocation(cas.e, cas.n, cas.scale * 5);
	//Map.refresh();
	this.nodes[1].innerHTML = title;
	this.nodes[2].innerHTML = dates;//subtitle;
	this.nodes[3].innerHTML = org;
	this.nodes[4].innerHTML = shortDesc + '... <a href="HighImpactDetail.aspx?id=' + id + '">read more</a>';
	this.nodes[5].innerHTML = "";
	this.point.x = Math.round(Map.width / 2) + 40;
	this.point.y = Math.round(Map.height / 2);
	this.setPosition(null);
	this.hidePrevNextButtons();
	this.show();
	return false;
}
IdentifyObject.prototype.showOnMap = function(ix) {
	var texts = this.arrIdentifyTexts[ix].split("$");
	this.nodes[1].innerHTML = texts[0]; // title
	this.nodes[2].innerHTML = texts[1]; // location
	this.nodes[3].innerHTML = texts[2]; // dates
	this.nodes[4].innerHTML = texts[3]; // org
	this.nodes[5].innerHTML = ""; //texts[4]; // description
	var xy = this.arrIdentifyCoords[ix].split(",");
	// calculate the x/y coords
	this.point.x = parseInt(xy[0]);
	this.point.y = parseInt(xy[1]);
	this.setPosition(null);
	this.hidePrevNextButtons();
	this.show();
	return false;
}
IdentifyObject.prototype.showTextInWin = function(x,y,textHTML) {
	this.point.x = parseInt(x);
	this.point.y = parseInt(y);
	this.setPosition(null);
	this.nodes[1].innerHTML = "";
	this.nodes[2].innerHTML = textHTML;
	this.nodes[3].innerHTML = "";
	this.nodes[4].innerHTML = "";
	this.nodes[5].innerHTML = "";
	this.show();
}
IdentifyObject.prototype.showLoading = function(pnt) {
	if (this.loading) return;
	this.loading = true;
	this.hidePrevNextButtons();
	this.nodes[1].innerHTML = "Loading roadwork details..." // title
	this.nodes[2].innerHTML = "please wait"; // location
	this.nodes[3].innerHTML = ""; // dates
	this.nodes[4].innerHTML = ""; // org
	this.nodes[5].innerHTML = ""; // description
	// calculate the x/y coords
	this.point.x = pnt.x;
	this.point.y = pnt.y;
	this.setPosition(null);
	this.show();
	var ajax = new AJAXObject();
	var identifyUrl = "Identify.aspx?w="+Map.width + "&h="+Map.height + "&e="+Map.e + "&n="+Map.n + "&scale="+Map.scale + "&x=" + pnt.x + "&y=" + pnt.y + "&datespan=" + Map.datespan + "&rnd="+Math.random();
	ajax.loadXMLDoc(identifyUrl, function() {
		if (ajax.req) {
			if (ajax.req.readyState == 4) {
				// only if "OK"
				if (ajax.req.status == 200) {
					var s = ajax.req.responseText;
					Identify.prevNextContent = s.split("~~~");
					Identify.showPrevNextContent(0);
					if (Identify.prevNextContent.length == 1)
						Identify.hidePrevNextButtons();
					else
						Identify.showPrevNextButtons();
				} else {
					Identify.nodes[1].innerHTML = "Error "+ajax.req.status;
				}
				Identify.loading = false;
			}
		}
	});
	return false;
}
IdentifyObject.prototype.hidePrevNextButtons = function() {
	this.buttons[0].style.visibility = "hidden";
	this.buttons[1].style.visibility = "hidden";
}
IdentifyObject.prototype.showPrevNextButtons = function() {
	this.buttons[0].style.visibility = "";
	this.buttons[1].style.visibility = "";
}
IdentifyObject.prototype.showPrevNextContent = function(ix) {
	this.currentPrevNextIndex = ix;
	var texts = this.prevNextContent[ix].split("$");
	Identify.nodes[1].innerHTML = texts[0]; // title
	Identify.nodes[2].innerHTML = texts[1]; // location
	Identify.nodes[3].innerHTML = texts[2]; // dates
	Identify.nodes[4].innerHTML = texts[3]; // org
	Identify.nodes[5].innerHTML = ""; //texts[4]; // description
}
IdentifyObject.prototype.Prev = function() {
	if (this.currentPrevNextIndex != 0)
		this.showPrevNextContent(this.currentPrevNextIndex - 1);
	else
		alert("No more roadworks found");
}
IdentifyObject.prototype.Next = function() {
	if (this.currentPrevNextIndex != this.prevNextContent.length - 1)
		this.showPrevNextContent(this.currentPrevNextIndex + 1);
	else
		alert("No more roadworks found");
}
IdentifyObject.prototype.setPosition = function(addPoint) {
	if (addPoint == null)
		addPoint = new CoordsScreen(0,0);
	this.infoWin.style.marginLeft = (this.point.x + addPoint.x) - 287;
	this.infoWin.style.marginTop  = (this.point.y + addPoint.y) - 136;
}
IdentifyObject.prototype.drift = function(driftBy) {
	// onMouseMove
	this.setPosition(driftBy);
}
IdentifyObject.prototype.shift = function(shiftBy) {
	// onMouseUp
	this.point.x += shiftBy.x;
	this.point.y += shiftBy.y;
	this.setPosition(null);
	if (this.point.x > Map.width || this.point.x < 0 || this.point.y > Map.height || this.point.y < 0)
	{
		this.hide();
	}
}
IdentifyObject.prototype.imgIdentify = function(ix) {
	alert(this.arrIdentifyTexts[ix]);
	return false;
}
IdentifyObject.prototype.updateIdentify = function() {
	var extent = Map.getExtent();
	var ajax = new AJAXObject();
	var identifyUrl = "Identify.aspx?w="+Map.width + "&h="+Map.height + "&e="+Map.e + "&n="+Map.n + "&scale="+Map.scale + "&datespan=" + Map.datespan + "&rnd="+Math.random();
	document.getElementById("divIdentifySubPage").innerHTML = "Loading...";
	ajax.loadXMLDoc(identifyUrl, function() {
		if (ajax.req) {
			if (ajax.req.readyState == 4) {
				// only if "OK" (status 200)
				if (ajax.req.status == 200) {
					var s = ajax.req.responseText;
					document.getElementById("divIdentifySubPage").innerHTML = s;
					// Fetch the coords and texts for the infobox
					var re = /(?:<div id="divIdentifyCoords">)([^<]*)(?:<\/div>)\s*(?:<div id="divIdentifyColors">)([^<]*)(?:<\/div>)\s*(?:<div id="divIdentifyTexts">)([^<]*)(?:<\/div>)/i;
					var matches = re.exec(s);
					if (matches == null)
						alert("Program error: can't find roadwork points; check for changes to identify.aspx");
					if (matches[1].length != 0)
					{
						Identify.arrIdentifyCoords = matches[1].split("|");
						Identify.arrIdentifyColors = matches[2].split("|");
						Identify.arrIdentifyTexts  = matches[3].split("|");
					} else {
						// no points found
					}
				} else {
					document.getElementById("divIdentifySubPage").innerHTML = "ERROR " + ajax.req.status + "<br />\n" + ajax.req.responseText;
					alert("Error " + ajax.req.status);
				}
			}
		}
	});
}

// ================== ADDRESS STUFF =============

function AddressObject() {
	this.addrWin = document.getElementById("divAddrResults");
	this.addrTbl = document.getElementById("tblAddressResults");
	this.addrBtn = document.getElementById("btnAddrSearch");
	this.clearBtn= document.getElementById("btnAddrClear");
	this.postBtn = document.getElementById("btnPostcode");
	this.loading = false;
}
AddressObject.prototype.disable = function() {
	this.addrBtn.innerText = "Searching, please wait...";
	this.addrBtn.disabled = "disabled";
	this.clearBtn.disabled= "disabled";
	this.postBtn.disabled = "disabled";
}
AddressObject.prototype.enable = function() {
	this.addrBtn.innerText = "Search for street";
	this.addrBtn.disabled = "";
	this.clearBtn.disabled= "";
	this.postBtn.disabled = "";
}
function doPostcodeSearch() {
	Address.disable();
	var postcode = encodeURIComponent(document.getElementById("txtPostcode").value);
	var postcodeUrl = "EmbeddedAddressFinder.aspx?mode=postcode&postcode=" + postcode;
	var ajax = new AJAXObject();
	ajax.loadXMLDoc(postcodeUrl, function() {
		if (ajax.req) {
			if (ajax.req.readyState == 4) { // only if "OK"
				Address.enable();
				if (ajax.req.status == 200) {
					var s = ajax.req.responseText;
					if (s.indexOf(",") == -1) {
						// not found or error message
						if (s.length < 3) s = "Unknown error";
						alert(s);
					} else {
						// found
						var xy = s.split(",");
						Address.view(parseFloat(xy[0]), parseFloat(xy[1]), null);
					}
					clearDropDownFlag = true;
				}
			}
		}
	});
}
function doAddressSearch() {
	Address.disable();
	clearDropDownFlag = false;
	// clear other searches
	document.getElementById("txtPostcode").value = "";
	document.getElementById("selTown").selectedIndex = 0;
	document.getElementById("selDistrict").selectedIndex = 0;
	// read search terms
	var sel = document.getElementById("selAddrTown");
	var town = encodeURIComponent(sel.options[sel.selectedIndex].value);
	var street = encodeURIComponent(document.getElementById("txtAddrStreet").value);
	if (street.length < 2) {
		if (town.length < 2) {
			alert("Please enter a street name");
			Address.enable();
			return;
		} else {
			Address.enable();
			var st = document.getElementById("selTown");
			st.selectedIndex = sel.selectedIndex + 1; // +1 because of CHESHIRE
			jumpToCoords(st);
			return;
		}
	}
	var addressUrl = "EmbeddedAddressFinder.aspx?mode=street&town=" + town + "&street=" + street;
	var ajax = new AJAXObject();
	ajax.loadXMLDoc(addressUrl, function() {
		if (ajax.req) {
			if (ajax.req.readyState == 4) {
				// only if "OK"
				Address.enable();
				if (ajax.req.status == 200) {
					var s = ajax.req.responseText;
					if (s.indexOf("%%") == -1) {
						// not found or error message
						Address.hide();
						if (s.length < 3) s = "Unknown error";
						alert(s);
					} else {
						// found
						// first, clear out old results
						Address.clear();
						// second, add new results
						var addresses = s.split("\n");
						for (var i=0; i<addresses.length; i++) {
							var pair = addresses[i].split("%%");
							var row = Address.addrTbl.insertRow();
							var cellA = row.insertCell();
							var cellB = row.insertCell();
							cellA.innerHTML = pair[0];
							cellB.innerHTML = pair[1];
						}
						// finally, show the entire div
						Address.show();
					}
				}
			}
		}
	});
}
AddressObject.prototype.show = function() {
	this.addrWin.style.display = "";
}
AddressObject.prototype.hide = function() {
	this.addrWin.style.display = "none";
}
AddressObject.prototype.clear = function() {
	for (var r=this.addrTbl.rows.length-1; r>=1; r--) {
		this.addrTbl.rows[r].deleteCell();
		this.addrTbl.rows[r].deleteCell();
		this.addrTbl.deleteRow();
	}
	document.getElementById("txtAddrStreet").value = "";
	document.getElementById("selAddrTown").selectedIndex = 0;
}
AddressObject.prototype.view = function(e,n,anchor) {
	// Move map
	ZoomMarker.moveToNewLocation(e, n, 2);
	// Translate map coords to screen point
	var newExtent = Map.getExtent();
	var x = (e - newExtent.minx) / Map.scale;
	var y = Map.height - ((n - newExtent.miny) / Map.scale);
	// Show info window, if possible
	if (anchor != null) {
		// Get address text
		var addressHTML = anchor.parentNode.previousSibling.innerHTML;
		// Hide prev/next arrow-buttons
		Identify.hidePrevNextButtons();
		// Show window
		Identify.showTextInWin(x, y, addressHTML);
	}
	return false;
}

// ------------ TABS -------------

function TabsObject() {
	//this.activeTabObj = null;
	this.activeTabIx = 2;
	this.identifyTabIx = 2;
	this.count = 5;
	this.activeTabHead = function() { return document.getElementById("tabHead" + this.activeTabIx); }
	this.activeTabBody = function() { return document.getElementById("tabBody" + this.activeTabIx); }
	this.isIdentify = function() { return this.activeTaxIx == identifyTabIx ? true : false; }
}
TabsObject.prototype.click = function(ix) {
	// Close all tabs except the one we want to open
	this.activeTabBody().className = "closed";
	this.activeTabIx = ix;
	this.activeTabBody().className = "opened";
	// Un-bold all tabs except the one we want to bold
	for (var i=1; i<=this.count; i++)
	{
		var obj = document.getElementById("tabHead" + i);
		if (i != ix && obj != null) {
			obj.parentNode.className = "";
		}
		else if (i == ix) {
			obj.parentNode.className = "active";
		}
	}
	return false;
}
TabsObject.prototype.mOver = function(ix) {
	var tab = document.getElementById("tabNote" + ix);
	if (tab != null) tab.style.display = "";
	return false;
}
TabsObject.prototype.mOut = function(ix) {
	var tab = document.getElementById("tabNote" + ix);
	if (tab != null) tab.style.display = "none";
	return false;
}
var Tabs = new TabsObject();

// Centred-on AJAX stuff
function AJAXObject() {
	this.req = false;
}
AJAXObject.prototype.loadXMLDoc = function(url, callbackFunction) {
	if (this.req !== false) {
		this.req.abort();
	}
	this.req = false;
	// branch for native XMLHttpRequest object
	if (window.XMLHttpRequest) {
		try {
			this.req = new XMLHttpRequest();
		} catch(e) {
			this.req = false;
		}
	// branch for IE/Windows ActiveX version
	} else if (window.ActiveXObject) {
		try {
			this.req = new ActiveXObject("Msxml2.XMLHTTP");
		} catch(e) {
			try {
				this.req = new ActiveXObject("Microsoft.XMLHTTP");
			} catch(e) {
				this.req = false;
			}
		}
	}
	if (this.req) {
		this.req.onreadystatechange = callbackFunction;
		this.req.open("GET", url, true);
		this.req.send("");
	}
}


// =====================

function LeftMenuObject() {
	this.direction = true; // true=opening, false=closing
	this.startPosition = -166;
	this.position = this.startPosition;
	this.step = 10;
	this.delay = 15; // lower = faster
}
LeftMenuObject.prototype.mOver = function() {
	this.direction = true;
	document.getElementById("divLeftMenu").style.height = (Map.height + 6) + "px";
	this.timer();
}
LeftMenuObject.prototype.mOut = function() {
	this.direction = false;
	this.timer();
}
LeftMenuObject.prototype.timer = function() {
	var bRepeat = true;
	if (this.direction) {
		this.position += this.step;
		if (this.position >= 0) {
			this.position = 0;
			bRepeat = false;
		}
	} else {
		this.position -= this.step;
		if (this.position <= this.startPosition)
		{
			this.position = this.startPosition;
			bRepeat = false;
		}
	}
	document.getElementById("divLeftMenu").style.left = this.position + "px";
	if (bRepeat)  window.setTimeout(LeftMenu_timer,this.delay);
	else if (!this.direction)
		document.getElementById("divLeftMenu").style.height = "350px";
}
function LeftMenu_timer() {
	LeftMenu.timer();
}
