﻿/*
 * Johnston Press Digital Publishing (Copyright 2008)
 * 
 * File:            Map.js
 * Description:     Lightweight AJAX map product.
 * 
 * Features:
 *      * Works on IE6, 7, FireFox and Opera.
 *      * No server side calls required, or database work when displaying map.
 *      * Printer friendly.
 *      * Can display at any arbitrary size.
 *
 * Revision History:
 *      04/01/2008 - steven.gray - Initial Draft 
 *      05/01/2008 - steven.gray - Further work and performance improvements.
 *      06/01/2008 - steven.gray - Add support for points of interest, and zoom levels 8-10.
 *      06/01/2008 - steven.gray - Corrected the scaling constants - They were massively incorrect, now much less so.
 *      06/01/2008 - steven.gray - Zoom level 11, mouse wheel support.
 *      06/01/2008 - steven.gray - Point of interest click handler support/generic eventing code.
 *      10/01/2008 - steven.gray - Increased tile selection radius to fix non-updating region issue, and hide bubble when map moves.
 *      11/01/2008 - steven.gray - Zoom in and zoom out hides bubble.
 *      30/01/2008 - steven.gray - When removing DOM elements, also removing event handlers. Should solve IE crash issue.
 *
*/
/* <![CDATA[ */
 var c_FetchData = 1;
 var c_intOtherBusiness = 0;

/// <summary>
///   Register an event handler for a control.
/// </summary>
/// <param name="objElement">Element to add event to</param>
/// <param name="strEventName">Event Name</param>
/// <param name="objFunction">Callback function</param>
function RegisterEventHandler(objElement, strEventName, objFunction)
{
    // Element might be the name, so check and translate.
    if(typeof(objElement) == "string")
        objElement = document.getElementById(objElement);
    
    // If we've still not got the element, exit.
    if(objElement == null)
        return;
        
    if (objElement.AttachedHandlers == null)
        objElement.AttachedHandlers = [];
    
    objElement.AttachedHandlers[strEventName] = objFunction;
    
    // If the browser supports the addEventListener model
    if(objElement.addEventListener) {
        // Due to FF/Opera wierdness, this one particular event has multiple names.
        if(strEventName == 'mousewheel') {
          objElement.addEventListener('DOMMouseScroll', objFunction, false);  
        }
        
        objElement.addEventListener(strEventName, objFunction, false);
    }
    else if(objElement.attachEvent) {
        objElement.attachEvent("on" + strEventName, objFunction);
        objElement.attachEvent(strEventName, objFunction);
    }
    else 
        alert("Cannot event wireup for " + strEventName);
}

/// <summary>
///    Reliable way to destroy an object tree.
/// </summary>
function DestroyRecursive(objElement) {
    if (objElement == null)
        return;
    if (objElement.childNodes == null)
        return;

    // Remove child notes
    for (var intIndex = objElement.childNodes.length-1; intIndex >= 0 && objElement.childNodes.length > 0; intIndex--)
    {
        DestroyRecursive(objElement.childNodes.item(intIndex));
    } 
    
    // Destroy this element
    UnregisterEventHandlers(objElement);
    if (objElement.outerHTML)
        objElement.outerHTML = '';
    objElement = null;
}

/// <summary>
///    Unregister any event handlers from an element.
/// </summary>
function UnregisterEventHandlers(objElement) {
    if (objElement == null)
        return;
        
   if (objElement.AttachedHandlers == null)
        return;
   var intDestroyed = 0;
   
   // Un-hook each event
   for (var strEventName in objElement.AttachedHandlers)
   {
        if (objElement.addEventListener) 
        {
            objElement.removeEventListener(strEventName, objElement.AttachedHandlers[strEventName], false);
            intDestroyed++;          
        }
        else if (objElement.attachEvent) {
            objElement.detachEvent("on" + strEventName, objElement.AttachedHandlers[strEventName]);
            intDestroyed++;          
        }
    }    
   
   // Finally, wipe the list
   objElement.AttachedHandlers = null; 
}

/// <summary>
///     A simple class that represents the zoom levels associated with mapping.
/// </summary>
/// <param name="strName">Name of Zoom Level</param>
/// <param name="strBaseUrl">Base URI of zoom level images</param>
/// <param name="dblScalingFactor">Scaling factor of OSGB points per pixel</param>
/// <param name="intTileCountHorizontal">Tiles in the X/Horizontal direction.</param>
/// <param name="intTileCountVertical">Tiles in the Y/Vertical direction.</param>
/// <param name="intTileWidth">Width of tiles.</param>
/// <param name="intTileHeight">Height of tiles.</param>
/// <param name="objPoint">Point that represents the centre of tile 0,0, the top left rendering tile. Used to calculate offsets.</param>
function JPMapZoomLevel(strName, strBaseUrl,dblScalingFactor, intTileCountHorizontal, intTileCountVertical, intTileWidth, intTileHeight, objPoint) {
	this.Name = strName;
	this.BaseUrl = strBaseUrl;

	// Number of map units per pixel. Lower values mean
	this.ScalingFactor = dblScalingFactor;

	// How many tiles are there in the set.
	this.TileCountHorizontal = intTileCountHorizontal;
	this.TileCountVertical = intTileCountVertical;

	// How many pixels for each tile?
	this.TileWidth = intTileWidth;
	this.TileHeight = intTileHeight;

    // How many units are represented by the tiles in each dimension
    this.UnitsPerTileHorizontal = this.TileWidth * this.ScalingFactor;
    this.UnitsPerTileVertical = this.TileHeight * this.ScalingFactor;

    // If objPoint represents the centre of tile 0,0, the top left tile, then calculate the top-left corner point of tile 0,0.
    
    // Horizontal/X axis is increasing from left to right, so subtract half the UnitsPerTileHorizontal 
    this.BoundLeft = objPoint.X - (this.UnitsPerTileHorizontal * 0.5);
    // Vertical/Y axis is decreasing from top to bottom, so add half the UnitsPerTileVertical
    this.BoundTop = objPoint.Y + (this.UnitsPerTileVertical * 0.5);
    
    // Function to calculate Top Left of specific tile.
    this.GetTopLeft = function(intRow, intCol) {
        var dblY = this.BoundTop - (intRow * this.UnitsPerTileVertical);
        var dblX = this.BoundLeft + (intCol * this.UnitsPerTileHorizontal);
        
        return new JPMapPoint(dblX, dblY); 
    }
    
    // Function to determine what tile contains a specific JPMapPoint
    this.FindTileForXY = function(objPoint) {
        var intTileRow = 0;
        var intTileCol = 0;
        
        for (var intRowIndex = 0; intRowIndex <= this.TileCountVertical; intRowIndex++) {
            // Since values decrease going down, we perform this check differently to the cols
            var objTopLeftOfTile = this.GetTopLeft(intRowIndex, 0);
            if ((objTopLeftOfTile.Y >= objPoint.Y) && (objPoint.Y >= (objTopLeftOfTile.Y-this.UnitsPerTileVertical))) {
                intTileRow = intRowIndex;
                break;
            }            
        }
        
        for (var intColIndex = 0; intColIndex <= this.TileCountHorizontal; intColIndex++) {
            var objTopLeftOfTile = this.GetTopLeft(0, intColIndex);
            if ((objTopLeftOfTile.X <= objPoint.X) && (objPoint.X <= (objTopLeftOfTile.X+this.UnitsPerTileHorizontal))) {
                intTileCol = intColIndex;
                break;
            }            
        }
        
        return new JPTile(intTileRow, intTileCol);
    };
}

/// <summary>
///    Map Tile
/// </summary>
/// <param name="intRow">Row/Y Coordinate</param>
/// <param name="intCol">Col/X Coordinate</param>
function JPTile(intRow, intCol) {
	this.Row = intRow;
	this.Column = intCol;
}

/// <summary>
///    Map Point
/// </summary>
/// <param name="intX">X Coordinate/Horizontal</param>
/// <param name="intY">Y Coordinate/Vertical</param>
function JPMapPoint(intX, intY) {
	this.X = intX;
	this.Y = intY;
}

/// <summary>
///    Point of interest on the map display.
/// </summary>
/// <param name="strIconPath">Icon Path</param>
/// <param name="intWidth">Icon width</param>
/// <param name="intHeight">Icon height</param>
/// <param name="objPoint">Point of interest.</param>
/// <param name="strToolTip">Tool Tip HTML</param>
/// <param name="objClickEvent">Callback event</param>
function JPPointOfInterest(strIconPath, intWidth, intHeight, objPoint, strToolTip, objClickEvent, intTopOffset, intLeftOffset) {
    this.Element = null;
    this.IconPath = strIconPath;
    this.Location = objPoint;
    this.Width = intWidth;
    this.Height = intHeight;
    this.ToolTip = strToolTip;
    
    if (objClickEvent == null)
        this.ClickEvent = function() { };
    else    
        this.ClickEvent = objClickEvent;
    
    if (intLeftOffset != null)
        this.ToolTipOffsetLeft = intLeftOffset;
    else
        this.ToolTipOffsetLeft = 0;
        
    if (intTopOffset != null)
        this.ToolTipOffsetTop = intTopOffset;
    else
        this.ToolTipOffsetTop = 0;
}

/// <summary>
///     Write a debug notification or otherwise alert the user/programmer.
/// </summary>
function DebugNotification(strMessage) {
	alert("Map Problem: " + strMessage);
}

/// <summary>
///    Find the accurate X coordinate of the mouse event.
/// </summary>
function GetMouseEventX(evt) {
    if (evt.pageX) 
        return evt.pageX;
    else if (evt.clientX)
       return evt.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
    else 
        return null;
}

/// <summary>
///    Find the accurate Y coordinate of the mouse event.
/// </summary>
function GetMouseEventY(evt) {
    if (evt.pageY) 
        return evt.pageY;
    else if (evt.clientY)
        return evt.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
    else 
        return null;
}

/// <summary>
///    Initialize a new instance of JPMap for the container with the specified ID.
/// </summary>
/// <param name="strControlId">ID of the control that contains the map.</param>
/// <param name="intInitialX">Initial X coordinate.</param>
/// <param name="intInitialY">Initial Y coordinate.</param>
function JPMap(strControlId, intInitialX, intInitialY, intWidth, intHeight) {
	if ((strControlId == null) || (strControlId == '')) {
		DebugNotification("Cannot initialize a map with an invalid control ID.");
		return;
	}

	this.BaseControl = document.getElementById(strControlId);
	if (this.BaseControl == null) {
		DebugNotification("Cannot find the specified control for a map: " + strControlId);
		return;
	}
	
	var objInstance = this;
	this.BaseControl.style.textAlign = "left";
	this.BaseControl.style.overflow = "hidden";
    	
	var objInterior = document.createElement('div');
		objInterior.setAttribute('id', strControlId + '_Layers');
		objInterior.className = 'Layers';
		objInterior.style.margin = '0px';
		objInterior.style.overflow = 'hidden';
		objInterior.style.position = 'absolute';	
		objInterior.style.height = intHeight + 'px';
		objInterior.style.width = intWidth + 'px';
		objInterior.style.padding = '0px';	
		this.BaseControl.appendChild(objInterior);

	// Create the tile scroll region inside	
	var objScrollRegion = document.createElement('div');
		objScrollRegion.setAttribute('id', strControlId + '_MapTiles');	
		objScrollRegion.className = 'MapTiles';
		objScrollRegion.style.margin = '0px';
		objScrollRegion.style.padding = '0px';
		objScrollRegion.style.position = 'absolute';		
		this.Control = objScrollRegion;
	
	// Create the POI region inside	
	var objControlPOI = document.createElement('div');
		objControlPOI.setAttribute('id', strControlId + '_PointsOfInterest');	
		objControlPOI.className = 'PointsOfInterest';
		objControlPOI.style.margin = '0px';
		objControlPOI.style.padding = '0px';
		objControlPOI.style.position ='absolute';
		objControlPOI.style.zIndex = 1;	
		this.ControlPOI = objControlPOI;
	
	// Create the tile scroll region inside	
	var objControlRegion = document.createElement('div');
		objControlRegion.setAttribute('id', strControlId + '_MapControls');	
		objControlRegion.className = 'MapControls';
		objControlRegion.style.margin = '0px';
		objControlRegion.style.padding = '0px';
		objControlRegion.style.position ='absolute';
		objControlRegion.style.zIndex = 2;	
		this.ControlPanel = objControlRegion;
		
	objInterior.appendChild(this.Control);
    objInterior.appendChild(this.ControlPOI);
    objInterior.appendChild(this.ControlPanel);
    
    
	// The base location for image data.
	this.ResourceBaseLocation = 'http://images.propertytoday.co.uk/mapimages/';
	
	// Pixel size
	this.Width = intWidth;
	this.Height = intHeight;

	// Location
	this.Location = new JPMapPoint(intInitialX, intInitialY);

	// Register the various supported zoom levels.
	this.ZoomLevels = [];
	this.ZoomLevels[1] = new JPMapZoomLevel('Zoom1', this.ResourceBaseLocation + 'Zoom1', 	2316.1403465066, 1, 1,     256, 256, new JPMapPoint(45547.96669, 767812.8935));
	this.ZoomLevels[2] = new JPMapZoomLevel('Zoom2', this.ResourceBaseLocation + 'Zoom2', 	1160.0835748137, 3, 3,     256, 256, new JPMapPoint(-104369.4812, 917730.3414));
	this.ZoomLevels[3] = new JPMapZoomLevel('Zoom3', this.ResourceBaseLocation + 'Zoom3', 	580.7331624257, 7, 7,     256, 256, new JPMapPoint(-179458.5271, 992819.3873));
	this.ZoomLevels[4] = new JPMapZoomLevel('Zoom4', this.ResourceBaseLocation + 'Zoom4', 	290.5564193579, 15, 15,   256, 256, new JPMapPoint(-217047.800917399, 1030408.66111308));
	this.ZoomLevels[5] = new JPMapZoomLevel('Zoom5', this.ResourceBaseLocation + 'Zoom5', 	145.3442052521, 31, 31,   256, 256, new JPMapPoint(-235854.7255, 1049215.586));
	this.ZoomLevels[6] = new JPMapZoomLevel('Zoom6', this.ResourceBaseLocation + 'Zoom6', 	72.6715550599, 63, 63,   256, 256, new JPMapPoint(-245262.4595, 1058623.32));
	this.ZoomLevels[7] = new JPMapZoomLevel('Zoom7', this.ResourceBaseLocation + 'Zoom7', 	36.3380638562, 127, 127, 256, 256, new JPMapPoint(-249966.291092393, 1063327.15128808));
	this.ZoomLevels[8] = new JPMapZoomLevel('Zoom8', this.ResourceBaseLocation + 'Zoom8', 	18.1759439184, 255, 255, 256, 256, new JPMapPoint(-252318.3549, 1065679.215));
	this.ZoomLevels[9] = new JPMapZoomLevel('Zoom9', this.ResourceBaseLocation + 'Zoom9', 	9.0844357981, 511, 511,  256, 256, new JPMapPoint(-253494.8341, 1066855.694)); 
	this.ZoomLevels[10] = new JPMapZoomLevel('Zoom10', this.ResourceBaseLocation + 'Zoom10',4.5425694489, 1023, 1023,  256, 256, new JPMapPoint(-254082.8448945520, 1067443.7050902500)); 
	this.ZoomLevels[11] = new JPMapZoomLevel('Zoom11', this.ResourceBaseLocation + 'Zoom11',2.2708979218 , 2047,2047,  256, 256, new JPMapPoint(-254376.8730261570, 1067737.7332218500)); 
	
    this.CurrentZoomIndex = 3;
    
    // Points of interest.
    this.PointsOfInterest = [];
    
	// Function for rendering a single tile.
	this.Tiles = [];
	this.AddTile = function(objFocusedLocation, objZoom, objTile) {
		if (objFocusedLocation == null) {
			DebugNotification("JPMap::AddTile - Focused Location not specified");
			return;
		}
		if (objZoom == null) {
			DebugNotification("JPMap::AddTile - Current zoom not specified");
			return;
		}
		if (objTile == null) {
			DebugNotification("JPMap::AddTile - Current tile not specified.");
			return;
		}

		var strName = objZoom.Name + '_R' + objTile.Row + '_C'+objTile.Column;
		if (this.Tiles[strName] != null) {
			// Tile already exists, return it.
			return this.Tiles[strName];
		}
		
		// Create the div that contains the tile and format it.
		var objCurrentTile = document.createElement('div');
		objCurrentTile.setAttribute('id', strName);	
		objCurrentTile.className = 'MapTileHolder';
		objCurrentTile.style.margin = '0px';
		objCurrentTile.style.padding = '0px';
		objCurrentTile.style.position = 'absolute';
		objCurrentTile.style.width = objZoom.TileWidth + 'px';
		objCurrentTile.style.height = objZoom.TileHeight + 'px';
		objCurrentTile.unselectable = "on";

		objCurrentTile.TileRow = objTile.Row;
		objCurrentTile.TileColumn = objTile.Column;
		
        // Calculate pixel count from bottom left up to top right
        var dblUnitRangeX = this.CurrentZoomLevel.TileCountHorizontal * this.CurrentZoomLevel.TileWidth * this.CurrentZoomLevel.ScalingFactor;
        var dblUnitRangeY = this.CurrentZoomLevel.TileCountVertical * this.CurrentZoomLevel.TileHeight * this.CurrentZoomLevel.ScalingFactor;
        
		// Calculate what map coordinate range this tile really represents	
		var objTileTopLeft = this.CurrentZoomLevel.GetTopLeft(objCurrentTile.TileRow, objCurrentTile.TileColumn);
		
		// Our map tiles are rendered X:1-x top-bottom, Y:1-y left-right, but our coordinate
		// system is based from bottom left and working up.
	    objCurrentTile.BoundLeft = objTileTopLeft.X;
	    objCurrentTile.BoundTop = objTileTopLeft.Y;
	    objCurrentTile.BoundRight = objTileTopLeft.X + this.CurrentZoomLevel.UnitsPerTileHorizontal;
	    objCurrentTile.BoundBottom = objTileTopLeft.Y - this.CurrentZoomLevel.UnitsPerTileVertical;
	    
		// Add tile image into the div.

		var objImage = document.createElement('div');
        objImage.style.background = "url('" + objZoom.BaseUrl + '/' + objTile.Row + '/' + objTile.Column + '.png' + "')";
                
//		var objImage = document.createElement('img');
//		objImage.src = objZoom.BaseUrl + '/' + objTile.Row + '/' + objTile.Column + '.png';
        objImage.unselectable = "on";
        objImage.style.height = this.ZoomLevels[this.CurrentZoomIndex].TileHeight + 'px';
        objImage.style.width = this.ZoomLevels[this.CurrentZoomIndex].TileWidth + 'px';
        
		// Add the object to the DOM and tile cache.
		objCurrentTile.appendChild(objImage);
		this.Control.appendChild(objCurrentTile);
		this.Tiles[strName] = objCurrentTile;

		return objCurrentTile;
	};

	// Update control clip information
	this.UpdateControlBounds = function() {

		// Determine whereabouts the centre point is in this.Control.
		var dblHalfWidth = this.Width / 2;
		var dblHalfHeight = this.Height / 2;
		
		var dblWidthFactor = (this.CurrentZoomLevel.ScalingFactor * dblHalfWidth);
		var dblHeightFactor = (this.CurrentZoomLevel.ScalingFactor * dblHalfHeight);

		// Scale these offsets by the zoom level scaling factor.
		var dblTopLeftCoordinateX = this.Location.X - dblWidthFactor;
		var dblTopLeftCoordinateY = this.Location.Y + dblHeightFactor;
		
		// Tag the control with the coordinates that the viewport represents.
		this.Control.BoundLeft = dblTopLeftCoordinateX;
		this.Control.BoundTop = dblTopLeftCoordinateY;
		this.Control.BoundRight = dblTopLeftCoordinateX + (2 * dblWidthFactor);
		this.Control.BoundBottom = dblTopLeftCoordinateY - (2 * dblHeightFactor);
	};

	// Calculate the coordinate represented by 0, 0 in this.Control
	this.ZeroPosition = function() {
		if (this.Control.BoundLeft == null)
			this.UpdateControlBounds();

		return new JPMapPoint(this.Control.BoundLeft, this.Control.BoundTop);
	};

	// Calculate the JPMapPoint X/Y (Top, left) offsets for a specific tile,
	// in relation to this.Location and this.CurrentZoomLevel
	this.CalculateRelativeTileViewPosition = function(objTile) {	
		if (objTile == null) {
			DebugNotification("JPMap::CalculateRelativeTileViewPosition - No tile object specified.");
			return;
		}
		    		
		var dblViewPortRangeHorizontal = this.Control.BoundLeft - this.Control.BoundRight;
		var dblScaleX = dblViewPortRangeHorizontal / this.Width;
		var dblOffsetTop = (this.Control.BoundLeft - objTile.BoundLeft) / dblScaleX; 
		objTile.style.left = dblOffsetTop + 'px';

		var dblViewPortRangeVertical = this.Control.BoundBottom - this.Control.BoundTop;
	    var dblScaleY = dblViewPortRangeVertical / this.Height;
		var dblOffsetLeft = (objTile.BoundTop - this.Control.BoundTop) / dblScaleY;
		objTile.style.top = dblOffsetLeft + 'px';
	};
	
	this.CalculatePointOfInterestViewPosition = function(objPoi) {
		if (objPoi == null) {
			DebugNotification("JPMap::CalculatePointOfInterestViewPosition - No tile object specified.");
			return;
		}
	
		var dblViewPortRangeHorizontal = this.Control.BoundLeft - this.Control.BoundRight;
		var dblScaleX = dblViewPortRangeHorizontal / this.Width;
		var dblOffsetTop = ((this.Control.BoundLeft - objPoi.Location.X) / dblScaleX) - (0.5*objPoi.Height); 
		objPoi.Element.style.left = dblOffsetTop + 'px';

		var dblViewPortRangeVertical = this.Control.BoundBottom - this.Control.BoundTop;
	    var dblScaleY = dblViewPortRangeVertical / this.Height;
		var dblOffsetLeft = ((objPoi.Location.Y - this.Control.BoundTop) / dblScaleY) - (0.5*objPoi.Width);
		objPoi.Element.style.top = dblOffsetLeft + 'px';
	};

	// Function for rendering the map.
	this.RenderMap = function() { 
	    this.CurrentZoomLevel = this.ZoomLevels[this.CurrentZoomIndex];
        if (this.CurrentZoomLevel == null) {
            DebugNotification("JPMap::RenderMap - Current Zoom Level is not set.");
            return;   
        }
        
        //if (this.c_intOtherBusiness == 1){
        
        
		this.UpdateControlBounds();
        
        // Get the tile that represents the current focus point.
        var objTileCentre = this.CurrentZoomLevel.FindTileForXY(this.Location);	
		var dblTilesHorizontal = Math.max(this.Width / this.CurrentZoomLevel.TileWidth, 1);
		var dblTilesVertical = Math.max(this.Height / this.CurrentZoomLevel.TileHeight, 1);
		
		// Calaculate the tile ranges to display.
		var minRow = objTileCentre.Row - Math.ceil(dblTilesVertical/2);
		var maxRow = objTileCentre.Row + Math.ceil(dblTilesVertical/2);
	    var minCol = objTileCentre.Column -Math.ceil(dblTilesHorizontal/2);
	    var maxCol = objTileCentre.Column + Math.ceil(dblTilesHorizontal/2);
		
		// Lets render the focus-tile first, just to make things seem a little snappier.		
        var objFocusTile = this.AddTile(this.Location, this.CurrentZoomLevel, new JPTile(objTileCentre.Row, objTileCentre.Column));
        this.CalculateRelativeTileViewPosition(objFocusTile);
            		    
		// Render the tiles in the current view.	    
		// Todo: Prioritize based on distance - Peripheral tiles last?
		for (var intRow = minRow; intRow <= maxRow; intRow++) {
		    if ((intRow >= 0) && (intRow <= this.CurrentZoomLevel.TileCountVertical)) {
			    for (var intCol = minCol; intCol <= maxCol; intCol++) {
		            if ((intCol >= 0) && (intCol <= this.CurrentZoomLevel.TileCountHorizontal)) {
                        var objTile = this.AddTile(this.Location, this.CurrentZoomLevel, new JPTile(intRow, intCol));
            		}
		        }
		    }
		}	
		
		for (var tileKey in this.Tiles)
		    this.CalculateRelativeTileViewPosition(this.Tiles[tileKey]);
            		    
		
		// Add points of interest icons or jitter existing ones.
		for (var intIndex in this.PointsOfInterest)
		{
		    var objPoi = this.PointsOfInterest[intIndex];
		    if (objPoi.Element == null) {
		        objPoi.Element = document.createElement("img");
                objPoi.Element.setAttribute("src", objPoi.IconPath);
                objPoi.Element.style.position = 'absolute';
                objPoi.Element.style.width = objPoi.Width + 'px';
                objPoi.Element.style.height = objPoi.Height + 'px';
                objPoi.Element.LocationX = objPoi.Location.X;
                objPoi.Element.LocationY = objPoi.Location.Y;

                this.ControlPOI.appendChild(objPoi.Element);                
                if (objPoi.ToolTip != null) {
                        RegisterEventHandler(objPoi.Element, "mouseover", function(event) { objInstance.ShowBubble(objPoi, event); });
                        RegisterEventHandler(objPoi.Element, "mouseout", function(event) { objInstance.HideBubble(objPoi, event);  });
                }
                if (objPoi.ClickEvent != null) {
                    RegisterEventHandler(objPoi.Element, "dblclick", function(event) { objPoi.ClickEvent() });   
                }
		    }
		    // Reverse Render order.
            //objPoi.Element.style.zIndex = this.PointsOfInterest.length - intIndex;
            objPoi.Element.style.zIndex = intIndex;
		    this.CalculatePointOfInterestViewPosition(objPoi);
		}
	};
	
    this.DisplayPointOfInterestTip = function(objPointOfInterest, newImage) {
        objPointOfInterest.Element.setAttribute("src", newImage);
    };
    this.CancelPointOfInterestTip = function(objPointOfInterest) {
            objPointOfInterest.Element.setAttribute("src", objPointOfInterest.IconPath);
    };
    
    // Show the tooltip bubble for a point of interest.
    this.ShowBubble = function(objPointOfInterest, event) {
        this.PinBubble = false;
        this.HideBubble(objPointOfInterest, event);
        
        // Unregister and re-register any events.
        UnregisterEventHandlers(objPointOfInterest.Element);
        RegisterEventHandler(objPointOfInterest.Element, "mouseover", function(event) { objInstance.ShowBubble(objPointOfInterest, event); });
        RegisterEventHandler(objPointOfInterest.Element, "mouseout", function(event) { objInstance.HideBubble(objPointOfInterest, event);  });
        RegisterEventHandler(objPointOfInterest.Element, "click", function(event) { objInstance.PinBubble = !objInstance.PinBubble; });
        RegisterEventHandler(objPointOfInterest.Element, "dblclick", function(event) { objPointOfInterest.ClickEvent(); });
                        
        this.ActiveBubble = document.createElement("div");
        this.ActiveBubble.style.position = 'absolute';
        this.ActiveBubble.style.left = (GetMouseEventX(event) + objPointOfInterest.ToolTipOffsetLeft) + 'px';
        this.ActiveBubble.style.top = (GetMouseEventY(event)+ objPointOfInterest.ToolTipOffsetTop)  + 'px';
        this.ActiveBubble.innerHTML = objPointOfInterest.ToolTip;       
        this.ActiveBubble.style.zIndex = 100; 
        document.body.appendChild(this.ActiveBubble);
    };
    
    // Hide the tooltip bubble for a point of interest.
    this.HideBubble = function(objPointOfInterest, event) {        
        if (this.ActiveBubble != null) {
            if (this.PinBubble == false) {        
                DestroyRecursive(this.ActiveBubble);
                UnregisterEventHandlers(this.ActiveBubble);
                try { document.body.removeChild(this.ActiveBubble); } 
                catch(E) { };
                window.status = 'destroyed child';        
                this.ActiveBubble = null;
            }
        }
    };
    
    
    // Remove all displayed tiles.
    this.ClearTiles = function() {
        for (var objTile in this.Tiles) {      
           UnregisterEventHandlers(this.Tiles[objTile]);
           this.Control.removeChild(this.Tiles[objTile]);
        }
        this.Tiles = [];
    };
    
    // Zoom in one level.
    this.ZoomIn = function() {
    //debugger;
        if (this.ZoomLevels[this.CurrentZoomIndex+1] == null)
            return;
            
              if (this.ZoomLevels[this.CurrentZoomIndex+1].Name == "Zoom10"){
                document.getElementById("RestaurantsInMap").disabled = false;
                document.getElementById("HotelsInMap").disabled = false;
                document.getElementById("CinemasInMap").disabled = false;
                document.getElementById("VenuesInMap").disabled = false;
                this.c_FetchData = 1;
             }
             
        this.PinBubble = false;
        this.HideBubble(null, null);
            
        this.CurrentZoomIndex++;
        this.ClearTiles();
        this.RenderMap();
      
        if ((c_intOtherBusiness == 1) && (this.CurrentZoomIndex > 9)){
             wctlVenueDetails.GetBusinesses(parseInt( this.Control.BoundLeft), parseInt(this.Control.BoundRight), parseInt(this.Control.BoundTop), parseInt(this.Control.BoundBottom), c_intBusinessCategoryID, Callback);
        }
           
      
    };
    
    // Zoom out one level.
    this.ZoomOut = function() {
    //debugger;
        if (this.ZoomLevels[this.CurrentZoomIndex-1] == null)
            return;
          
            if (this.ZoomLevels[this.CurrentZoomIndex-1].Name == "Zoom9"){
                document.getElementById("RestaurantsInMap").disabled = true;
                document.getElementById("HotelsInMap").disabled = true;
                document.getElementById("CinemasInMap").disabled = true;
                document.getElementById("VenuesInMap").disabled = true;
                this.c_FetchData = 0;
             }
              
            
        this.PinBubble = false;
        this.HideBubble(null, null);
        this.CurrentZoomIndex--;
        this.ClearTiles();
        this.RenderMap();
      
        if ((c_intOtherBusiness == 1) && (this.CurrentZoomIndex > 8)) {
             wctlVenueDetails.GetBusinesses(parseInt( this.Control.BoundLeft), parseInt(this.Control.BoundRight), parseInt(this.Control.BoundTop), parseInt(this.Control.BoundBottom), c_intBusinessCategoryID, Callback);
        }
      
       
        
    };
    
    // Move the map up 25% of the height
    this.MoveUp = function() {
        this.Location.Y -= (0.25 * (this.Control.BoundBottom- this.Control.BoundTop));
        this.PinBubble = false;
        this.HideBubble(null, null);
        this.RenderMap();
        
         if ((c_intOtherBusiness == 1) && (this.CurrentZoomIndex > 8)) {
             wctlVenueDetails.GetBusinesses(parseInt( this.Control.BoundLeft), parseInt(this.Control.BoundRight), parseInt(this.Control.BoundTop), parseInt(this.Control.BoundBottom), c_intBusinessCategoryID, Callback);
        }
    };
    
    // Move the map down 25% of the height.
    this.MoveDown = function() {
        this.Location.Y += (0.25 * (this.Control.BoundBottom - this.Control.BoundTop));
        this.PinBubble = false;
        this.HideBubble(null, null);
        this.RenderMap();
        
         if ((c_intOtherBusiness == 1) && (this.CurrentZoomIndex > 8)) {
             wctlVenueDetails.GetBusinesses(parseInt( this.Control.BoundLeft), parseInt(this.Control.BoundRight), parseInt(this.Control.BoundTop), parseInt(this.Control.BoundBottom), c_intBusinessCategoryID, Callback);
        }
    };
    
    // Move the map left 25% of the width.
    this.MoveLeft = function() {
    //debugger;
        this.Location.X -= (0.25 * (this.Control.BoundRight - this.Control.BoundLeft));
        this.PinBubble = false;
        this.HideBubble(null, null);
        this.RenderMap();
        
         if ((c_intOtherBusiness == 1) && (this.CurrentZoomIndex > 8)) {
             wctlVenueDetails.GetBusinesses(parseInt( this.Control.BoundLeft), parseInt(this.Control.BoundRight), parseInt(this.Control.BoundTop), parseInt(this.Control.BoundBottom), c_intBusinessCategoryID, Callback);
        }
    };    
    
    // Move the map right 25% of the width.
    this.MoveRight = function() {
        this.Location.X += (0.25 * (this.Control.BoundRight - this.Control.BoundLeft));
        this.PinBubble = false;
        this.HideBubble(null, null);
        this.RenderMap();
        
         if ((c_intOtherBusiness == 1) && (this.CurrentZoomIndex > 8)) {
             wctlVenueDetails.GetBusinesses(parseInt( this.Control.BoundLeft), parseInt(this.Control.BoundRight), parseInt(this.Control.BoundTop), parseInt(this.Control.BoundBottom), c_intBusinessCategoryID, Callback);
        }
    };
    
    // Add a visual map control element.
    this.AddControl = function(strImagePath, intTop, intLeft, intWidth, intHeight, strAction) {
        var objElement = document.createElement("img");
        var objMap = this;
         
        objElement.setAttribute("src", strImagePath);
        objElement.style.position = "absolute";
        objElement.style.left = intLeft + "px";
        objElement.style.top = intTop + "px";
        switch(strAction) {
            case "ZoomIn":
                objElement.onclick = function() { objMap.ZoomIn(); };    
                break;
            case "ZoomOut":
                objElement.onclick = function() { objMap.ZoomOut(); };
                break;
            case "MoveLeft":
                objElement.onclick = function() { objMap.MoveLeft(); };    
                break;
            case "MoveRight":
                objElement.onclick = function() { objMap.MoveRight(); };
                break;
            case "MoveUp":
                objElement.onclick = function() { objMap.MoveUp(); };    
                break;
            case "MoveDown":
                objElement.onclick = function() { objMap.MoveDown(); };
                break;
        }
        
        
        this.ControlPanel.appendChild(objElement);
    };

    // Add a point of interest.
    this.AddPointOfInterest = function(objItem) {
        this.PointsOfInterest[this.PointsOfInterest.length] = objItem;
        this.RenderMap();
    };
    
    // Remove a specific point of interest.
    this.RemovePointOfInterest = function(intIndex) {
        var objItem = this.PointsOfInterest[intIndex];
        if ((objItem == null) || (objItem.Element == null))
            return;
            
        // Destroy the POI
        DestroyRecursive(objItem.Element);
        // Remove from array as deleted.
        this.PointsOfInterest.splice(intIndex, 1)
    };

    // Remove all points of interest
    this.RemoveAllPointsOfInterest = function() {
        for (var intIndex = this.PointsOfInterest.length-1; intIndex >= 1; intIndex--) {
            var objCurrentPoi = this.PointsOfInterest[intIndex];
            this.RemovePointOfInterest(intIndex);            
        }

        this.PinBubble = false;
        this.HideBubble(null, null);

        this.RenderMap();
    };
    
    // Without moving the current 'Location', adjust the zoom to the minimum that
    // displays all of the active points of interest. Null operation if no POI's.
    this.AutoZoom = function() {
        if (this.PointsOfInterest.length == 0)
            return;
        
        var intOldZoom = this.CurrentZoomIndex;
        
        for (intZoomIndex = this.ZoomLevels.length-1; intZoomIndex >= 1 ; intZoomIndex--) {
            this.CurrentZoomIndex = intZoomIndex;
	        this.CurrentZoomLevel = this.ZoomLevels[this.CurrentZoomIndex];
    		this.UpdateControlBounds();  
    		
    		// Check each POI at the current zoom.
            var blnZoomOk = true;
            for (var intPoiIndex in this.PointsOfInterest) {
                var objPointOfInterest = this.PointsOfInterest[intPoiIndex];
                var objLocation = objPointOfInterest.Location;
                
                // Is the POI in the display field?
                if ((this.Control.BoundLeft < objLocation.X) && (objLocation.X < this.Control.BoundRight) 
                    && (this.Control.BoundTop > objLocation.Y) && (objLocation.Y > this.Control.BoundBottom)) {
                    // This zoom is OK!
                } 
                else {
                    // The pushpin was not in view.
                    blnZoomOk = false;    
                }
            }
            
            if (blnZoomOk) {
                this.ClearTiles();
                this.RenderMap();
                return;
            }              
        }
        
        // We've failed, so revert the zoom and rebound the box.
        this.CurrentZoomIndex = intOldZoom;        
		this.UpdateControlBounds();
    };
    
    // Register mouse wheel zooming
    this.WheelEvent = function(event) {	
        var dblChange = 0;
	    if (!event) 
	        event = window.event;
	    if (event.wheelDelta) {
		    dblChange = event.wheelDelta/120; 
	    } else if (event.detail) {
		    dblChange = -event.detail/3;
	    }
	    if (dblChange) {
	        if (dblChange > 0) {
	            this.ZoomIn();
	        } else {
	            this.ZoomOut();
	        }
	        event.returnValue=false
	    }
    };
   
    // Assume we're not dragging.
    this.Dragging = false;
    
    // Dragging start event.
    this.DragStart = function(event) {
        this.DragStartPointX = event.clientX;
        this.DragStartPointY = event.clientY;
        this.DragStartCursor = document.body.style.cursor;        
        this.Dragging = true;
    };
    
    // Dragging has stopped
    this.DragStop = function(event) {
        this.Dragging = false; 
        
         if ((c_intOtherBusiness == 1) && (this.CurrentZoomIndex > 8)) {
             wctlVenueDetails.GetBusinesses(parseInt( this.Control.BoundLeft), parseInt(this.Control.BoundRight), parseInt(this.Control.BoundTop), parseInt(this.Control.BoundBottom), c_intBusinessCategoryID, Callback);
        }
    };

    // Drag and drop events.
    this.DragMove = function(event) { 
        // Ensure drag termination for some edge cases.
        if (event.button == null) {
            this.Dragging = true;
        } 
        if (this.Dragging) {        
            var dblDifferenceX = event.clientX - this.DragStartPointX;
            var dblDifferenceY = event.clientY - this.DragStartPointY;
            this.DragStartPointX = event.clientX;
            this.DragStartPointY = event.clientY;
            var dblScaledHoriz = this.ZoomLevels[this.CurrentZoomIndex].ScalingFactor * dblDifferenceX;
            var dblScaledVert = this.ZoomLevels[this.CurrentZoomIndex].ScalingFactor * dblDifferenceY;
            
            this.Location.X -= dblScaledHoriz;
            this.Location.Y += dblScaledVert;
            this.PinBubble = false;
            this.HideBubble(null, null);
		    this.UpdateControlBounds();  
            this.RenderMap();        
        }
    };
    
    // Move to an area when the user double clicks.    
    this.MoveToPoint = function(event) {
        // TODO: Implement double click location jumping.
    };
    
    //RegisterEventHandler(this.Control, "mousewheel", function(event) { objInstance.WheelEvent(event); });       // Mouse-wheel zoom events.
    RegisterEventHandler(this.Control, "mousedown", function(event) { objInstance.DragStart(event); });         // When starting to click, start dragging.
    RegisterEventHandler(this.Control, "mouseup", function(event) { objInstance.DragStop(event); });            // When mouse is released, stop drag.
    RegisterEventHandler(this.Control, "mouseout", function(event) { objInstance.DragStop(event); });           // Terminate drag when leaving object.
    RegisterEventHandler(this.Control, "mousemove", function(event) { objInstance.DragMove(event); });          // Process dragging progress.
    RegisterEventHandler(this.Control, "drag", function(event) { objInstance.DragStart(event); });              // Browser drag events for some browsers.
    RegisterEventHandler(this.Control, "dblclick", function(event) { objInstance.MoveToPoint(event); });        // When the map is double clicked, jump to point.
    
	// Render the map for the first time
	this.RenderMap();
}
/* ]]> */