// June 20th 2005 http://civicactions.net anselm@hook.org public domain version 0.5

/*
	This code was taken from openstreetmap.org, and licensed under the GPL,
	therefore, this code is GPL

	andy@andybotting.com

*/

var netscape = ( document.getElementById && !document.all ) || document.layers;
var moz = (typeof document.implementation != 'undefined') && (typeof document.implementation.createDocument != 'undefined');
var ie = (typeof window.ActiveXObject != 'undefined');
var urlpath = "/";
var urlbase = "maps.civicactions.net";
var visiting = "home";
var parameters_array = null;
var xmlDoc;
var xmlHttp;
var defaultEngine = null; // xxx for firefox keyboard events.

var PI = 3.14159265358979323846;

// Utility - get div position - may not be accurate

function getCSSPositionX(parent) {
	var offset = parent.x ? parseInt(parent.x) : 0;
	offset += parent.style.left ? parseInt(parent.style.left) : 0;
	for(var node = parent; node ; node = node.offsetParent ) { offset += node.offsetLeft; }
	return offset;
}

function getCSSPositionY(parent) {
	var offset = parent.y ? parseInt(parent.y) : 0;
	offset += parent.style.top ? parseInt(parent.style.top) : 0;
	for(var node = parent; node ; node = node.offsetParent ) { offset += node.offsetTop; }
	return offset;
}


///
/// initialize a new tile engine object
/// usage: var engine = new tile_engine_new(parentdiv,stylehints,wmssource,lon,lat,zoom,optional width, optional height)
///
function tile_engine_new(parentname,hints,feedurl,url,lon,lat,zoom,marker,w,h) {

	// get parent div or fail
	this.parent = document.getElementById(parentname);
	if( this.parent == null ) {
		alert('The tile map engine cannot find a parent container named ['+parentname+']');
		return;
	}

	//
	// store for later
	//

	this.parentname = parentname;
	this.hints = hints;
	this.feedurl = feedurl;
	this.url = url;
	this.lon = lon;
	this.lat = lat;
	this.zoom = zoom;
	this.marker = marker;
	this.optionalw = w;
	this.optionalh = h;
	this.dragcontainer = 1;
	this.debug = 1;
	this.parent.tile_engine = this;

	// for firefox keyboard
	defaultEngine = this;
	document.engine = this;

	//
	// decide on display width and height
	//
	if( !w || !h ) {
		w = parseInt(this.parent.style.width);
		h = parseInt(this.parent.style.height);
		if(!w || !h) {
			w = 512;
			h = 256;
			this.parent.style.width = w + 'px';
			this.parent.style.height = h + 'px';
		}
	} else {
		this.parent.style.width = parseInt(w) + 'px';
		this.parent.style.height = parseInt(h) + 'px';
	}
	this.displaywidth = w;
	this.displayheight = h;


	// Andy: this code doesnt really work
	// Implemented zoom floor and ceiling values in zoom buttons
	this.minzoom = 1;
	this.maxzoom = 7;
	
	if (this.zoom < this.minzoom) 
	{ 
		this.zoom = this.minzoom; 
	}
	
	if (this.zoom > this.maxzoom) 
	{ 
		this.zoom = this.maxzoom; 
	}

	//
	// enforce parent div style?
	// position absolute is really only required for firefox
	// http://www.quirksmode.org/js/findpos.html
	//

	this.parent_x = getCSSPositionX(this.parent);
	this.parent_y = getCSSPositionY(this.parent);

	this.parent.style.position = 'relative';
	this.parent.style.overflow = 'hidden';
	this.parent.style.cursor = 'move';
	//this.parent.style.backgroundColor = '#C0C0C0';
	//this.parent.style.backgroundColor = '#FFFFFF';

	// it is possible that this collection is already in use - clean it

	while( this.parent.hasChildNodes() ) {
		this.parent.removeChild( this.parent.firstChild );
	}

	// build inner tile container for theoretical speed improvement?
	// center in parent for simplicity of math
	// size of inner container is irrelevant since overflow is enabled

	if( this.dragcontainer ) {
		this.tiles = document.createElement('div');
		this.tiles.style.position = 'absolute';
		this.tiles.style.left = this.displaywidth/2 + 'px';
		this.tiles.style.top = this.displayheight/2 + 'px';
		this.tiles.style.width = '16px';
		this.tiles.style.height = '16px';
		if( this.debug ) {
			this.tiles.style.border = 'dashed green 1px';
		}
		this.tiles.tile_engine = this;
		this.parent.appendChild(this.tiles);
	} else {
		this.tiles = this.parent;
	}

	// focus over specified lon/lat and zoom
	// user should call this.drag(0,0) after this to force an initial refresh

	this.performzoom = function(lon,lat,z) {
		// setup for zoom

		// In this function, we need to set the zoom level
		this.scale = 10000000;
		this.lon_min_clamp = -180 * this.scale;
		this.lon_max_clamp = 180 * this.scale;
		this.lat_min_clamp = -180 * this.scale; //t
		this.lat_max_clamp = 180 * this.scale; //t
		this.lon_start_tile = 180 * this.scale;
		this.lat_start_tile = 180 * this.scale; //t
		
		// Andy: Magic zoom factor levels for StreetDirectory maps
		var zoomfactor = new Array(0,39.2,14.65,8.143,6.86,6.73,5.5,4.7585);

		this.zoom_power = zoomfactor[zoom];
		this.lon_quant = this.lon_start_tile;
		this.lat_quant = this.lat_start_tile;
		this.lon = lon;
		this.lat = lat;

		// operational lat - = lat due to quirks in our engine and quirks in lon/lat design
		lat = -lat;

		// divide tile size until reach requested zoom
		while(z > 0) {
			this.lon_quant = this.lon_quant / this.zoom_power;
			this.lat_quant = this.lat_quant / this.zoom_power;
			z--;
		}

		// get user requested exact lon/lat
		this.lon_scaled = ( lon * this.scale );
		this.lat_scaled = ( lat * this.scale );
	
		// convert requested exact lon/lat to quantized lon lat (rounding down or up as best suits)
		this.lon_round = ( this.lon_scaled / this.lon_quant ) * this.lon_quant;
		this.lat_round = ( this.lat_scaled / this.lat_quant ) * this.lat_quant;

		// calculate world extents [ this is the span of all tiles in lon/lat ]
		this.lon_min = this.lon_round - this.lon_quant;
		this.lon_max = this.lon_round + this.lon_quant;
		this.lat_min = this.lat_round - this.lat_quant;
		this.lat_max = this.lat_round + this.lat_quant;
	
		// set tiled region details [ this is the span of all tiles in pixels ]
		this.centerx = 0;
		this.centery = 0;

		this.tilewidth = 256;
		this.tileheight = 256;

		// Andy: These variables can probably be removed, not used anymore.

		this.left = -this.tilewidth;
		this.right = this.tilewidth;
		this.top = -this.tileheight;
		this.bot = this.tileheight;
		
		// adjust the current center position slightly to reflect exact lat/lon not rounded
		this.centerx -= 0;
		this.centery -= 0;

	}

	/// update permalinks using DOM rather than using a JS link
	this.update_perma_link = function() {
		//updatelinks(this.lon,this.lat,this.zoom);

		var helper = this.navhelp; //document.getElementById( this.parentname + "_helper");
		if( helper ) {
			helper.innerHTML = "Zoom:" + this.zoom + " <a href='map.html?x=" + this.lon + "&y=" + this.lat + "&zoom=" + this.zoom + "&marker=" + this.marker +"'>Link</a>";
		}
	}

	/// draw the spanning lon/lat range
	/// drag is simply the mouse delta in pixels
	this.drag = function(dragx,dragy) {

		// move the drag offset
		this.centerx += dragx;
		this.centery += dragy;

		// update where we think the user is actually focused
		// Andy: this might be important
		this.lon = ( this.lon_round - ( this.lon_max - this.lon_min ) / ( this.right - this.left ) * this.centerx ) / this.scale;
		this.lat = - ( this.lat_round - ( this.lat_max - this.lat_min ) / ( this.bot - this.top ) * this.centery ) / this.scale;

		this.update_perma_link();


 		// extend exposed sections
		var dirty = false;
		while( this.left + this.centerx > -this.displaywidth/2 && this.lon_min > this.lon_min_clamp ) {
			this.left -= this.tilewidth;
			this.lon_min -= this.lon_quant;
			dirty = true;
		}
		while( this.right + this.centerx < this.displaywidth/2 && this.lon_max < this.lon_max_clamp ) {
			this.right += this.tilewidth;
			this.lon_max += this.lon_quant;
			dirty = true;
		}
		while( this.top + this.centery > -this.displayheight/2 && this.lat_min > this.lat_min_clamp ) {
			this.top -= this.tileheight;
			this.lat_min -= this.lat_quant;
			dirty = true;
		}
		while( this.bot + this.centery < this.displayheight/2 && this.lat_max < this.lat_max_clamp ) {
			this.bot += this.tileheight;
			this.lat_max += this.lat_quant;
			dirty = true;
		}

		// prepare to walk the container and assure that all nodes are correct
		var containerx;
		var containery;

		// in drag container mode we do not have to move the children all the time
		if( this.dragcontainer ) {
			this.tiles.style.left = this.displaywidth / 2 + this.centerx + 'px';
			this.tiles.style.top = this.displayheight / 2 + this.centery + 'px';
			if( !dirty && this.tiles.hasChildNodes() ) {
				return;
			}
			containerx = this.left;
			containery = this.top;
		} else {
			containerx = this.left + this.centerx;
			containery = this.top + this.centery;
		}

		// walk all tiles and repair as needed
		// xxx one bug is that it walks the _entire_ width and height... not just visible.
		// xxx this makes cleanup harder and perhaps a bitmap is better

		var removehidden = 1;
		var removecolumn = 0;
		var removerow = 0;
		var containeryreset = containery;

		for( var x = this.lon_min; x < this.lon_max ; x+= this.lon_quant ) {

			// will this row be visible in the next round?
			if( removehidden ) {
				var rx = containerx + this.centerx;
				if( rx > this.displaywidth / 2 ) {
					removerow = 1;
					// ideally i would truncate max width here
				} else if( rx + this.tilewidth < - this.displaywidth / 2 ) {
					removerow = 1;
				} else {
					removerow = 0;
				}
			}

			for( var y = this.lat_min; y < this.lat_max ; y+= this.lat_quant ) {

				// is this column visible?
				if( removehidden ) {
					var ry = containery + this.centery;
					if( ry > this.displayheight / 2 ) {
						removecolumn = 1;
					} else if( ry + this.tileheight < - this.displayheight / 2 ) {
						removecolumn = 1;
					} else {
						removecolumn = 0;
					}
				}

				// Create the lon/lat for the image request from the site
				// Andy: adjust the lat/lon to accommodate for actual lat/lon being the center of tile
				var mylon = ( ( x + ( this.lon_quant/2 ) ) / this.scale );
				var mylat = ( ( y - ( this.lat_quant/2 ) ) / this.scale );
				var bt = mylat + this.lat_quant / this.scale;
				var temp = bt;
				var bt = -mylat;
				var mylat = -temp;
				
				// Andy: Generate our Street Directory key here.
				// Add +30px to the height, so we can hide the green arrow in top corner

				var key = this.url + "?x=" + mylon + "&y=" + mylat + "&sizex=" + (this.tilewidth) + "&sizey=" + (this.tileheight+30) + "&level=" + this.zoom;

				// see if our tile is already present
				var node = document.getElementById(key);

				// create if not present
				if(!node) {

					// Create text to go under the image
					var text = document.createElement('img');
					text.style.position = 'absolute';
					text.src = 'images/loadingtile.gif';
					text.style.zIndex = -15; // Push the images to the back. without this, drag doesnt work

					// Create an image to go inside the div
					var img = document.createElement('img');
					img.style.position = 'absolute';
					img.style.top = '-30px';
					img.src = key;
					img.style.zIndex = -10; 
					// Push the images to the back. without this, drag doesnt work

					if (this.marker) {
					// Marker shadow
					var shadow = document.createElement('img');
					shadow.style.position = 'absolute';
					shadow.style.left = -12;
					shadow.style.top = -32;
					shadow.style.zIndex = 50;
					if( shadow.runtimeStyle ) {
						shadow.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/shadow.png');"
						shadow.src = 'images/transparentpixel.gif';
					} else { 
						shadow.src = 'images/shadow.png';
						shadow.style.opacity = .1;
					}

					// Marker
					var marker = document.createElement('img');
					marker.style.position = 'absolute';
					marker.style.left = -12;
					marker.style.top = -32;
					marker.style.zIndex = 51;
					if( marker.runtimeStyle ) {
						marker.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/marker.png');"
						marker.src = 'images/transparentpixel.gif';
					} else { 
						marker.src = 'images/marker.png';
					}
					} //if(this.marker)

					// Create the div element for tiles
					node = document.createElement('div');					
					node.id = key;
					node.className = 'tile';
					node.style.position = 'absolute';
					node.style.left = containerx + 'px';
					node.style.top = containery + 'px';
					node.style.width = this.tilewidth + 'px';
					node.style.height = this.tileheight + 'px';
					node.style.overflow = 'hidden'; // hide the extra 30px
					node.style.zIndex = 15; // to appear under the rss elements
					node.tile_engine = this;
					//node.style.border = '1px solid blue';
	

					// Andy: node is the DIV which contains the map tile, and also the loading tile image
					node.appendChild(img);
					node.appendChild(text);
				
					if (this.marker) {	
						this.tiles.appendChild(marker);
						this.tiles.appendChild(shadow);
					}

					this.tiles.appendChild(node);
					}

					// adjust if using active style
					else if( !this.dragcontainer ) {
					node.style.left = containerx + 'px';
					node.style.top = containery + 'px';
				}

				containery += this.tileheight;
			}
			containery = containeryreset;
			containerx += this.tilewidth;
		}

	}

	function getfield(content,field) {
		for(var subnode = content.firstChild; subnode != null; subnode = subnode.nextSibling ) {
			if( field == subnode.nodeName ) {
				if( subnode.nodeType == 1 ) {
					if( subnode.firstChild ) {
						return subnode.firstChild.data;
					}
				}
				break;
			}
		}
		return "";
	}


	// Andy: WTF is this shit for? I want to get rid of it, but not sure yet


	this.drawfeed = function(data) {

		if( data == null || data.firstChild == null) {
			return;
		}

		if( data.firstChild.nodeName == 'rdf:RDF') {
			data = data.firstChild;
		} else if( data.firstChild.nextSibling.nodeName == 'rdf:RDF' ) {
			data = data.firstChild.nextSibling;
		}

		for(var content = data.firstChild; content != null; content = content.nextSibling ) {

				if( content.nodeName != 'item' ) {
					continue;
				}


				var icon = getfield(content,"dc:thumb");
				var thumb = getfield(content,"dc:thumb");
				var lon = parseFloat(getfield(content,"geo:long"));
				var lat = parseFloat(getfield(content,"geo:lat"));
				var title = getfield(content,"title");
				var link = getfield(content,"link");
				var description = getfield(content,"description");

				// internal quirk
				lat = -lat;

				//var thumbtack_a = document.createElement('a');
				//var thumbtack_div = document.createElement('div');
				//thumbtack_a.href = link;
				//http://thingster.org/admin/.admin.jpg

				// draw where the update where we think the user is actually focused
				var x = ( lon * this.scale - this.lon_round ) * (this.right-this.left) / (this.lon_max - this.lon_min );
				var y = ( lat * this.scale - this.lat_round ) * (this.right-this.left) / (this.lon_max - this.lon_min );



				var img = document.createElement('img');
				img.style.position = "absolute";
				img.style.left = x+'px';
				img.style.top = y+'px';
				img.style.width = '30px';
				img.style.height = '20px';
				img.style.zIndex = 15; // to appear on top of the image elements
				img.border = 0;
				if( img.runtimeStyle ) {
					// http://redvip.homelinux.net/varios/explorer-png-en.html
					img.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='tile.png',sizingMethod='scale')";
					img.src = "transparentpixel.gif";
				} else {
					img.src = "tile.png";
				}
				var a = document.createElement('a');
				a.href = link;
				a.style.border = 0;
				this.tiles.appendChild(a);
			}
	}

	/// Setup the engine zoomed at a position to show a feed
	this.zoomtofeed = function(data) {

		// any content?
		if( data == null || data.firstChild == null) {
			this.performzoom(0,0,0);
			return;
		}

		if( data.firstChild.nodeName == 'rdf:RDF') {
			data = data.firstChild;
		} else if( data.firstChild.nextSibling.nodeName == 'rdf:RDF' ) {
			data = data.firstChild.nextSibling;
		}

		// get extents

		var success=0;
		var maxlon=0,maxlat=0,minlon=0,minlat=0;

		for(var content = data.firstChild; content != null; content = content.nextSibling ) {

			if( content.nodeName != 'item' ) {
				continue;
			}

			var lon = parseFloat(getfield(content,"geo:long"));
			var lat = parseFloat(getfield(content,"geo:lat"));

			if( maxlon < lon || success == 0) maxlon = lon;
			if( minlon > lon || success == 0) minlon = lon;
			if( maxlat < lat || success == 0) maxlat = lat;
			if( minlat > lat || success == 0) minlat = lat;
			success = 1;
		}

		// calculate zoom required to reach extent

		var lon = (maxlon-minlon)/1.9;
		var lat = (maxlat-minlat)/1.9;
		var zoomaxis = Math.sqrt( lon*lon + lat*lat );
		lon += minlon;
		lat += minlat;
		var zoom = 0;
		var zoomquant = Math.sqrt( 180 * 180 + 90 * 90 );
		while( zoomaxis < zoomquant && zoom < 13) {
			zoomquant = zoomquant / 2;
			zoom++;
		}

		// xxx think more about this; had to back out a bit - use y axis as base? w*w+h*h?
		if( zoom > 0 ) zoom--;

		// set current zoom
		this.zoom = zoom;

		// perform zoom
		if( success ) {
			this.performzoom(lon,lat,zoom);
		} else {
			this.performzoom(0,0,0);
		}

		this.zoom = zoom;

	}


	// Andy: And this shit too. What the hell is this for?

	/// immediately draw and or fit to feed
	if( feedurl ) {
		var data = importXML( feedurl );
		if( data && hints && hints.indexOf('FIT') >= 0 ) {
			this.zoomtofeed(data);
			this.drag(0,0);
			this.drawfeed(data);
		} else {
			this.performzoom(lon,lat,zoom);
			this.drag(0,0);
			this.drawfeed(data);
		}
	} else {
		this.performzoom(lon,lat,zoom);
		this.drag(0,0);
	}

	this.update_perma_link();

	/// intercept context events to minimize out-of-browser interruptions
	this.event_context = function(e) {
		return false;
	}

	// Andy: key codes for moving the map in IE. I think it should go.
	// Its kinda cool, but pointless when you can drag
	
	this.event_key = function(e) {
		var key = 0;		

		var hostengine = defaultEngine;

		if( window && window.event && window.event.srcElement ) {
			hostengine = window.event.srcElement.tile_engine;
		} else if( e.target ) {
			hostengine = e.target.tile_engine;
		} else if( e.srcElement ) {
			hostengine = e.srcElement.tile_engine;
		}

		if( hostengine == null ) {
			hostengine = defaultEngine;
			if( hostengine == null ) {
				return;
			}
		}

		if( e == null && document.all ) {
			e = window.event;
		}

		if( e ) {
			if( e.keyCode ) {
				key = e.keyCode;
			}
			else if( e.which ) {
				key = e.which;
			}

			switch(key) {
			case 97: // a = left
				hostengine.drag(16,0);
				break;
			case 100: // d = right
				hostengine.drag(-16,0);
				break;
			case 119: // w = up
				hostengine.drag(0,16);
				break;
			case 120: // x = dn
				hostengine.drag(0,-16);
				break;
			case 115: // s = center
				new tile_engine_new(hostengine.parentname,
							"FULL",
							hostengine.feedurl, // xxx hrm, cache this?
							hostengine.url,
							hostengine.lon,
							hostengine.lat,
							hostengine.zoom,
							0,0
							);
				break;
			case 122: // z = zoom
				new tile_engine_new(hostengine.parentname,
							"FULL",
							hostengine.feedurl, // xxx hrm, cache this?
							hostengine.url,
							hostengine.lon,
							hostengine.lat,
							(parseInt(hostengine.zoom) + 1),
							0,0
							);
				break;
			case  99: // c = unzoom
				new tile_engine_new(hostengine.parentname,
							"FULL",
							hostengine.feedurl, // xxx hrm, cache this?
							hostengine.url,
							hostengine.lon,
							hostengine.lat,
							(parseInt(hostengine.zoom) - 1),
							0,0
							);
				break;
			}
		}	
	}




	// Catch mouse move events

	this.event_mouse_move = function(e) {

		var hostengine = null;
		if( window && window.event && window.event.srcElement ) {
			hostengine = window.event.srcElement.tile_engine;
		} else if( e.target ) {
			hostengine = e.target.tile_engine;
		} else if( e.srcElement ) {
			hostengine = e.srcElement.tile_engine;
		}

		if( hostengine && hostengine.drag ) {
			if( hostengine.mousedown ) {
				if( netscape ) {
					hostengine.mousex = parseInt(e.pageX) + 0.0;
					hostengine.mousey = parseInt(e.pageY) + 0.0;
				} else {
					hostengine.mousex = parseInt(window.event.clientX) + 0.0;
					hostengine.mousey = parseInt(window.event.clientY) + 0.0;
				}
				hostengine.drag(hostengine.mousex-hostengine.lastmousex,hostengine.mousey-hostengine.lastmousey);
			}
			hostengine.lastmousex = hostengine.mousex;
			hostengine.lastmousey = hostengine.mousey;
		}

		// must return false to prevent operating system drag and drop from handling events
		return false;
	}

	/// catch mouse down
	this.event_mouse_down = function(e) {
		
		var hostengine = null;
		if( window && window.event && window.event.srcElement ) {
			hostengine = window.event.srcElement.tile_engine;
		} else if( e.target ) {
			hostengine = e.target.tile_engine;
		} else if( e.srcElement ) {
			hostengine = e.srcElement.tile_engine;
		}

		if( hostengine ) {
			if( netscape ) {
				hostengine.mousex = parseInt(e.pageX) + 0.0;
				hostengine.mousey = parseInt(e.pageY) + 0.0;
			} else {
				hostengine.mousex = parseInt(window.event.clientX) + 0.0;
				hostengine.mousey = parseInt(window.event.clientY) + 0.0;
			}
			hostengine.lastmousex = hostengine.mousex;
			hostengine.lastmousey = hostengine.mousey;
			hostengine.mousedown = 1;
		}

		// must return false to prevent operating system drag and drop from handling events
		return false;
	}

	/// catch double click (use to center map)
	this.event_double_click = function(e) {

		var hostengine = null;
		if( window && window.event && window.event.srcElement ) {
			hostengine = window.event.srcElement.tile_engine;
		} else if( e.target ) {
			hostengine = e.target.tile_engine;
		} else if( e.srcElement ) {
			hostengine = e.srcElement.tile_engine;
		}

		if( hostengine ) {
			if( netscape ) {
				hostengine.mousex = parseInt(e.pageX) + 0.0;
				hostengine.mousey = parseInt(e.pageY) + 0.0;
			} else {
				hostengine.mousex = parseInt(window.event.clientX) + 0.0;
				hostengine.mousey = parseInt(window.event.clientY) + 0.0;
			}
			var dx = hostengine.mousex-(hostengine.displaywidth/2)-hostengine.parent_x;
			var dy = hostengine.mousey-(hostengine.displayheight/2)-hostengine.parent_y;
			hostengine.drag(-dx,-dy); // TODO smooth
		}

		// must return false to prevent operating system drag and drop from handling events
		return false;

	}

	/// catch mouse up
	this.event_mouse_up = function(e) {

		var hostengine = null;
		if( window && window.event && window.event.srcElement ) {
			hostengine = window.event.srcElement.tile_engine;
		} else if( e.target ) {
			hostengine = e.target.tile_engine;
		} else if( e.srcElement ) {
			hostengine = e.srcElement.tile_engine;
		}

		if( hostengine ) {
			if( netscape ) {
				hostengine.mousex = parseInt(e.pageX) + 0.0;
				hostengine.mousey = parseInt(e.pageY) + 0.0;
			} else {
				hostengine.mousex = parseInt(window.event.clientX) + 0.0;
				hostengine.mousey = parseInt(window.event.clientY) + 0.0;
			}
			hostengine.mousedown = 0;
			hostengine.update_perma_link();
		}

		// must return false to prevent operating system drag and drop from handling events
		return false;
	}

	/// catch mouse out
	this.event_mouse_out = function(e) {

		var hostengine = null;
		if( window && window.event && window.event.srcElement ) {
			hostengine = window.event.srcElement.tile_engine;
		} else if( e.target ) {
			hostengine = e.target.tile_engine;
		} else if( e.srcElement ) {
			hostengine = e.srcElement.tile_engine;
		}

		if( hostengine ) {
			if( netscape ) {
				hostengine.mousex = parseInt(e.pageX) + 0.0;
				hostengine.mousey = parseInt(e.pageY) + 0.0;
			} else {
				hostengine.mousex = parseInt(window.event.clientX) + 0.0;
				hostengine.mousey = parseInt(window.event.clientY) + 0.0;
			}
			hostengine.mousedown = 0;
			hostengine.update_perma_link();
		}

		// must return false to prevent operating system drag and drop from handling events
		return false;
	}

	/// zoom a tile group
	this.tile_engine_zoomout = function(e) {
		var hostengine = this.tile_engine;

		if( window && window.event && window.event.srcElement ) {
			hostengine = window.event.srcElement.tile_engine;
		} else if( e.target ) {
			hostengine = e.target.tile_engine;
		} else if( e.srcElement ) {
			hostengine = e.srcElement.tile_engine;
		}

		if( hostengine.zoom > hostengine.minzoom ) {
			if( hostengine ) {
				new tile_engine_new(hostengine.parentname,
								"FULL",
								hostengine.feedurl, // xxx hrm, cache this?
								hostengine.url,
								hostengine.lon,
								hostengine.lat,
								hostengine.zoom-1,
								(parseInt(hostengine.zoom)-1),
								0,0
								);
			}
		}
    return false; // or safari falls over

	}

	/// zoom a tile group
	this.tile_engine_zoomin = function(e) {
		var hostengine = this.tile_engine;

		if( window && window.event && window.event.srcElement ) {
			hostengine = window.event.srcElement.tile_engine;
		} else if( e.target ) {
			hostengine = e.target.tile_engine;
		} else if( e.srcElement ) {
			hostengine = e.srcElement.tile_engine;
		}

		if( hostengine.zoom < hostengine.maxzoom ) {
			if( hostengine ) {
				new tile_engine_new(hostengine.parentname,
								"FULL",
								hostengine.feedurl, // xxx hrm, cache this?
								hostengine.url,
								hostengine.lon,
								hostengine.lat,
								(parseInt(hostengine.zoom)+1),
								0,0
								);
			}

		}

    return false; // or safari falls over
	}

	/// register new handlers to catch desired events
	this.event_catch = function(component) {

		if( netscape ) {
			window.captureEvents(Event.MOUSEMOVE);
			window.captureEvents(Event.KEYPRESS);
		}

		if( component ) {
			component.onmousemove = this.event_mouse_move;
			component.onmousedown = this.event_mouse_down;
			component.onmouseup = this.event_mouse_up;
			component.onkeypress = this.event_key;
			window.ondblclick = this.event_double_click;
		}

		if( window ) {
			window.onmousemove = this.event_mouse_move;
			window.onmouseup = this.event_mouse_up;
			window.ondblclick = this.event_double_click;
		}

	}

	this.loadXMLHTTP = function() {
	    /*@cc_on @*/
	    /*@if (@_jscript_version >= 5)
	    // JScript gives us Conditional compilation, we can cope with old IE versions.
	    // and security blocked creation of the objects.
	    try {
	    this.xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
	    } catch (e) {
	    try {
	    this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	    } catch (E) {
	    this.xmlhttp = false;
	    }
	    }
	    @end @*/
	    if (!this.xmlhttp && typeof XMLHttpRequest!='undefined') {
		this.xmlhttp = new XMLHttpRequest();
	    }
	}

	this.getNodeValue = function(obj, nodeName) {
	    var st = "";
	    if (obj.hasChildNodes()) {
		var i = 0;
		while ((st == "") && (i < obj.childNodes.length)) {
		    st = (obj.childNodes[i].nodeName == nodeName) ? obj.childNodes[i].firstChild.nodeValue : this.getNodeValue(obj.childNodes[i], nodeName);
		    i++;
		}
	    }
	    return st;
	}

	// attach event capture parent div
	this.event_catch(this.parent);

	// draw navigation buttons into the parent div
	this.navform = document.createElement('form');
	this.navform.name = parentname + "_form";
	this.navform.style.position = 'absolute';
	this.navform.style.zIndex = 15;
	this.navform.tile_engine = this;

	this.navout = document.createElement('input');
	this.navout.name = parentname + "_out";
	this.navout.type = "image";
	if( this.navout.runtimeStyle ) {
		this.navout.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/map_zoomout.png')";
		this.navout.src = 'images/transparentpixel.gif';
	} else {
		this.navout.src = "images/map_zoomout.png";
	}
	this.navout.value = "out";
	this.navout.style.zIndex = 99;
	this.navout.style.cursor = this.zoom <= 1 ? 'arrow' : 'hand';
	//this.navout.onclick = this.zoom == 0 ? 0 : this.tile_engine_zoomout;

  	this.navout.onclick = this.tile_engine_zoomout;

  	this.navout.tile_engine = this;
	this.navform.appendChild(this.navout);

	this.navin = document.createElement('input');
	this.navin.name = parentname + "_in";
	this.navin.type = "image";
	if( this.navin.runtimeStyle ) {
		this.navin.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/map_zoomin.png')";
		this.navin.src = 'images/transparentpixel.gif';
	} else {
	this.navin.src = "images/map_zoomin.png";
	}
	this.navin.value = "in";
	this.navin.style.zIndex = 99;
	this.navin.style.cursor = this.zoom >= 7 ? 'arrow' : 'hand';

	this.navin.onclick = this.tile_engine_zoomin;
  
	this.navin.tile_engine = this;
	this.navform.appendChild(this.navin);

	// Andy: The info text at the top L-hand corner

	this.navhelp = document.createElement('div');
	this.navhelp.name = parentname + "_helper";
	this.navhelp.style.display = 'inline';
	this.navhelp.style.color = 'black';
	this.navhelp.style.background = 'white';
	this.navhelp.innerHTML = '[ Please select and drag to move map ]'

	this.navform.appendChild(this.navhelp);

	this.parent.appendChild(this.navform);

}

