/**
 * MDVMap (mdvJavaScriptMapComponent)
 * Implementation of scrollable map component
 *
 * Software Version History:
 *
 * 1.1	2006/08/03	TI		HEAD:		Version Added. Initial Revision.
 * 1.2	2006/08/03	TI		HEAD:		MDV Map Tiles Layer Added.
 * 1.3	2006/08/04	TI		HEAD:		Map Information Added.
 * 1.4	2006/08/04	TI		HEAD:		Map Centering Added.
 * 1.5	2006/08/06	TI		HEAD:		Tile Loading Added.
 * 1.6	2006/08/06	TI		HEAD:		Basic API Functions Added (zoom & centre).
 * 1.7	2006/08/07	TI		HEAD:		Removal of Rows Added.
 * 1.8	2006/08/07	TI		HEAD:		Addition/Removal of Columns Added.
 * 1.9	2006/08/08	TI		HEAD:		Gadgets Added.
 * 1.10	2006/08/10	TI		HEAD:		Fixed Column Removing.
 * 1.11 2006/08/10	TI		HEAD:		Resizing Added.
 * 1.12 2006/08/17	TI		HEAD:		Refactoring.
 * 1.13 2006/08/22	TI		HEAD:		Added oncontextmenu Bubbling.
 * 1.14 2006/08/23	TI		HEAD:		Map Boundaries.
 * 1.15	2006/08/23	TI		HEAD:		Px to Real Coord.
 * 1.16 2006/08/30	TI		HEAD:		Extended Event Bubbling.
 * 1.17	2006/09/06	TI		HEAD:		Continuous Panning.
 * 1.18	2006/09/14	TI		HEAD:		Refactoring of _updateVisibleMap.
 * 1.19 2006/09/18	TI		HEAD:		Quick Fix Preventing Tile Inconsistency.
 * 1.20 2006/09/21	TI		HEAD:		MaxFailCount to Ensure Tile Loading.
 * 1.21 2006/09/21	TI		HEAD:		Layer Refactoring.
 * 1.22 2006/10/12	TI		HEAD:		Layer Refactoring.
 * 1.23	2006/10/20	TI		HEAD:		SetCoords() does Update.
 * 1.24 2006/11/09	TI		HEAD:		ToolTip Enhancements.
 * 1.25 2006/11/10	TI		HEAD:		MDVTimer added.
 * 1.26 2006/12/01	TI		HEAD:		New Event for Marker.
 * 1.27 2006/12/06	TI		HEAD:		Enhancements of ToolTip Extends.
 * 1.28	2006/12/27	TI		HEAD:		Delayed ToolTip Displaying.
 *
 */

var MDVEventID 						= 0;
var MDVEvent_ERROR 					= MDVEventID++;
var MDVEvent_DEBUG 					= MDVEventID++;
var MDVEvent_WARNING 				= MDVEventID++;
var MDVEvent_INITIALISED 			= MDVEventID++;
var MDVEvent_MAP_INITIALISED		= MDVEventID++;
var MDVEvent_ZOOM_CHANGED 			= MDVEventID++;
var MDVEvent_CENTRE_CHANGED			= MDVEventID++;
var MDVEvent_CONTEXT_MENU 			= MDVEventID++;
var MDVEvent_GADGET_REGISTERED		= MDVEventID++;
var MDVEvent_LAYER_ADDED			= MDVEventID++;
var MDVEvent_LAYER_UPDATED			= MDVEventID++;
var MDVEvent_AJAX_CALLBACK			= MDVEventID++;
var MDVEvent_MOUSE_MOVED			= MDVEventID++;
var MDVEvent_TOOLTIP				= MDVEventID++;
var MDVEvent_OBJECT_CLICKED			= MDVEventID++;
var MDVEvent_ROUTE_TRACE_FINISHED	= MDVEventID++;

 function MDVMap(viewport, name) {
 	// Check for browser capabilities
 	this.isCSS 		= false;
 	this.isDOM 		= false;
 	this.isIE4 		= false;
 	this.isNN4 		= false;
 	this.isIE6CSS 	= false;

 	if (document.images){
		this.isCSS 		= (document.body && document.body.style) ? true : false;
		this.isDOM 		= (this.isCSS && document.getElementById) ? true : false;
		this.isIE4 		= (this.isCSS && document.all) ? true : false;
		this.isNN4 		= (document.layers) ? true : false;
		this.isIE6CSS 	= (document.compatMode && document.compatMode.indexOf('CSS1') >= 0) ? true : false;
 	}

	// Object to store map configuration. Will be populated in method 'execute'.
 	this.config = null;

 	// Name of Map Instance
 	this.name = name;

 	// We store the view port object
 	this.viewport = viewport;

 	// Reference to the map within viewport
 	this.viewport.mdvMap = this;

 	// We also store the mdv map tiles object; will be populated in '_setLayers'
 	this.mapTiles = null;

 	// DIV of markers
 	this.markerObjects = null;

 	// Layer of markers
 	this.markerLayer = null;

	// Dimensions of a tile in reality
 	this.tileRealWidth  = -1;
 	this.tileRealHeight = -1;

 	// Number of visible tiles in view port
 	this.tileCountWidth  = -1;
 	this.tileCountHeight = -1;

 	// First tile of current view port
 	this.startTileWidth  = -1;
 	this.startTileHeight = -1;

 	// Number of img tags in mdvMapTiles
 	this.imgCountWidth  = 0;
	this.imgCountHeight = 0;

 	// Dimensions of visible area in reality
 	this.viewportRealWidth  = -1;
 	this.viewportRealHeight = -1;

 	// Size of overlapping on boundaries
 	this.boundarySize = 20;

 	// Coordinates of left and upper corner (view port)
 	this.tlReal = null;

 	// Coords of the left upper tile (not the intersecting point with the the view port as above!)
 	this.tlTileReal = null;

 	// The TL offset in pixel coords
 	this.offset = null;

 	// Wrapping offset
 	this.wrapOffset = null;

 	// Coordinates of the map centre
 	this.real = new MDVCoordinates();

 	// We need to set hidden to the overlow style because we don't want to see scroll bars.
 	// Additionally we need to set the position to relative to keep the map within our view port.
 	this.viewport.style.overflow = 'hidden';
 	this.viewport.style.position = 'relative';

 	// Size of map view port
 	this.viewportHeight = -1;
 	this.viewportWidth  = -1;

 	// Track map state (-1: Not initialised; 0: Initialising; 1: initialised)
 	this.state = -1;

	// Unique Image ID Counter
	this.lastImageID = 0;

	// Unique Marker ID Counter
	this.lastMarkerID = 0;

	// Unique Layer ID Counter
	this.lastLayerID = 0;

	// Unique Tool Tip ID Counter
	this.lastToolTipID = 0;
	
	// Unique Polyline Counter
	this.lastPolylineID = 0;

	// Max Fail Count
	this.maxFailCount = 3;

	// Placeholder: transparent gif
	this.transparent = new Image(1, 1);

	// The Div Element which stores all layers
	this.mapper = null;

 	// We need a object to manage our events
 	this.events = new MDVEvents();

 	// Gadgets
 	this.gadgets = new Array();

 	// Layers
 	this.layers = new Array();

 	// Renderer
 	this.renderer = null;

 	// Moving Stuff
 	this.speed = 1;
 	this.stepTime = 45;
 	this.stepPx = 4.3;
 	this.steps = null;
 	this.moveJobID = null;
 	this.crossHairSrc = new Image();
 	this.crossHair = null;
 	
	// register events
	for (var i=0; i<MDVEventID; i++) {
		this.events.addEventID(i);
	}
 }
 
 MDVMap.prototype.getViewportExtends = function () {
 	return { width: this.viewportWidth, height: this.viewportHeight };
 };

 MDVMap.prototype.getPoint = function(coords) {
 	if (coords == null)
 		return null;

 	// Update map values...
 	this._updateMapValues();

	var zoomLevel 			= this.config.getZoomLevel(this.config.currentZoomLevelIndex);
	var pxWidthAll          = parseInt(zoomLevel.get('tileSizeX')) * parseInt(zoomLevel.get('numberOfTilesX'));
	var pxToRealRatioWidth  = pxWidthAll / parseInt(zoomLevel.get('realWidth'));
	var pxHeightAll         = parseInt(zoomLevel.get('tileSizeY')) * parseInt(zoomLevel.get('numberOfTilesY'));
	var pxToRealRatioHeight = pxHeightAll / parseInt(zoomLevel.get('realHeight'));

	var retPoint = new MDVPoint(
		Math.floor(0.5 + (coords.x - this.tlTileReal.x) * pxToRealRatioWidth),
		Math.floor(0.5 + (coords.y - this.tlTileReal.y) * pxToRealRatioHeight));

		retPoint.x = retPoint.x - this.wrapOffset.x;
		retPoint.y = retPoint.y - this.wrapOffset.y;

	return retPoint;
 };

 MDVMap.prototype.getCoordinates = function(point) {
 	if (point == null)
 		return null;

	// Update map values...
 	this._updateMapValues();

	var zoomLevel 			= this.config.getZoomLevel(this.config.currentZoomLevelIndex);
	var pxWidthAll          = parseInt(zoomLevel.get('tileSizeX')) * parseInt(zoomLevel.get('numberOfTilesX'));
	var realToPxRatioWidth  = parseInt(zoomLevel.get('realWidth')) / pxWidthAll;
	var pxHeightAll         = parseInt(zoomLevel.get('tileSizeY')) * parseInt(zoomLevel.get('numberOfTilesY'));
	var realToPxRatioHeight = parseInt(zoomLevel.get('realHeight')) / pxHeightAll;

	var retCoord = new MDVCoordinates(this.config.get('mapName'),
		Math.floor(0.5 + this.tlReal.x + (point.x * realToPxRatioWidth)),
		Math.floor(0.5 + this.tlReal.y + (point.y * realToPxRatioHeight)));

	return retCoord;
 };

 MDVMap.prototype.createMarker = function(coords, factor, imgSrc) {
 	if (imgSrc != '')
	 	return new MDVMarker(this, coords, imgSrc, factor);
	else
		return null;
 };

 MDVMap.prototype.createToolTip = function(innerHTML) {
 	return new MDVToolTip(this, innerHTML);
 };

 MDVMap.prototype._updateMarkers = function() {

 	for (var l=0; l < this.layers.length; l++) {
	 	this.layers[l].update();
 	}

 	return true;
 };

 MDVMap.prototype.addMarker = function(marker) {
 	return this.markerLayer.addMarker(marker);
 };

 MDVMap.prototype.removeMarker = function(marker) {
 	return this.markerLayer.removeMarker(marker);
 };

 MDVMap.prototype.addLayer = function(layer) {
 	var found = false;

 	for (var l=0; l < this.layers.length; l++) {
 		if (this.layers[l].id == layer.id)
 			found = true;
 	}

 	if (!found) {
 		this.layers.push(layer);
		this.events.triggerEvent(MDVEvent_LAYER_ADDED, 'MDVMap has added a new layer (' + this.layers[l].name + ')', this.layers[l]);
		this.layers.append = true;
		// Check whether we need to update markers within layer.
		layer.update();
 	}
 };

 MDVMap.prototype.getLayer = function(name) {
 	for (var l=0; l < this.layers.length; l++) {
 		if (this.layers[l].name == name)
 			return this.layers[l];
 	}

 	return null;
 };

 MDVMap.prototype.getLayers = function () {
 	return this.layers;
 };

 MDVMap.prototype.registerGadget = function(gadget) {
 	var found = false;

 	for (var g=0; g < this.gadgets.length; g++) {
 		// We do not want to add double entries.
 		if (this.gadgets[g].name == gadget.name)
 			found = true;
 	}

 	// As well as we do not want to at the base class.
 	if (gadget.name == 'GadgetBase')
 		found = true;

 	if (!found) {
	 	this.gadgets.push(gadget);
	 	this.events.triggerEvent(MDVEvent_GADGET_REGISTERED, 'MDVMap registered a new gadget (' + gadget.name + ')', gadget);
 	}
 };

 MDVMap.prototype.getCentre = function() {
 	if (this.real.x > 0 && this.real.y > 0)
	 	return this.real;
	else
		return null;
 };

 MDVMap.prototype.setCentre = function(centre) {
	// Do we already have a map centre? No? - Set new centre.
 	if (centre && (!this.getCentre() || centre.x != this.getCentre().x || centre.y != this.getCentre().y)) {
 		var ret = false;
 		if (this._checkBoundariesX(centre)) {
	 		this.real.x = centre.x;
	 		this.real.mapName = centre.mapName;
	 		ret = true;
 		}
	 	if (this._checkBoundariesY(centre)) {
	 		this.real.y = centre.y;
	 		this.real.mapName = centre.mapName;
	 		ret = true;
	 	}

 		if (ret) this.events.triggerEvent(MDVEvent_CENTRE_CHANGED, 'MDVMap centre has been changed', centre);

//		fnMap_AppendHistory(this); //roccas append history

 		return ret;
 	} else {
 		return false;
 	}
 };

 MDVMap.prototype._checkBoundariesX = function(coord) {
	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());

 	var realOffsetX 		= parseInt(zoomLevel.get('realOffsetX'));
  	var realWidth 			= parseInt(zoomLevel.get('realWidth'));
	var pxWidthAll          = parseInt(zoomLevel.get('tileSizeX')) * parseInt(zoomLevel.get('numberOfTilesX'));
	var realToPxRatioWidth  = realWidth / pxWidthAll;

  	if (coord.x >= realOffsetX &&
  		coord.x <= (realOffsetX + realWidth))
  		return true;
  	else
  		return false;
 };

 MDVMap.prototype._checkBoundariesY = function(coord) {
	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());

  	var realOffsetY 		= parseInt(zoomLevel.get('realOffsetY'));
  	var realHeight 			= parseInt(zoomLevel.get('realHeight'));
	var pxHeightAll         = parseInt(zoomLevel.get('tileSizeY')) * parseInt(zoomLevel.get('numberOfTilesY'));
	var realToPxRatioHeight = realHeight / pxHeightAll;

  	if (coord.y >= realOffsetY &&
  		coord.y <= (realOffsetY + realHeight))
  		return true;
  	else
  		return false;
 };

 MDVMap.prototype.setZoomLevel = function(zoomLevelIndex) {
 	var zoomLevel = this.config.getZoomLevel(zoomLevelIndex);

 	if (this.config.getZoomLevelIndex() != zoomLevelIndex && zoomLevel) {
 		this.config.setZoomLevelIndex(zoomLevelIndex);

		rMapNavigator.fnSetScale(zoomLevel.scale, this.name);
		
 		this.events.triggerEvent(MDVEvent_ZOOM_CHANGED , 'MDVMap zoom level has been changed', zoomLevelIndex);

 		return true;
 	} else {

		if (!zoomLevel)
			this.events.triggerEvent(MDVEvent_ERROR, 'MDVMap wasn\'t able to set new zoom level.');
 		return false;
 	}
 };

 MDVMap.prototype.getObjWidth = function(obj)  {
    var result = 0;
    if (obj.offsetWidth) {
        result = obj.offsetWidth;
    } else if (obj.clip && obj.clip.width) {
        result = obj.clip.width;
    } else if (obj.style && obj.style.pixelWidth) {
        result = obj.style.pixelWidth;
    }
    return parseInt(result);
};

MDVMap.prototype.getObjHeight = function(obj)  {
    var result = 0;
    if (obj.offsetHeight) {
        result = obj.offsetHeight;
    } else if (obj.clip && obj.clip.height) {
        result = obj.clip.height;
    } else if (obj.style && obj.style.pixelHeight) {
        result = obj.style.pixelHeight;
    }
    return parseInt(result);
};

MDVMap.prototype._setBackgroundColour = function(color) {
	this.viewport.style.backgroundColor = color;
};

MDVMap.prototype.getName = function () {
	return this.name;
};

MDVMap.prototype.resize = function() {
	// Check whether function has already been invoked
	if (this.state == 0) {
		this.events.triggerEvent(MDVEvent_WARNING, 'MDVMap is already loading... please wait.');
		return false;
	}
	else if(this.state == 1) {
		this.events.triggerEvent(MDVEvent_ERROR, 'MDVMap has already been invoked.');
		return false;
	}

	// If we have a embeeded map with dynamical demensions we need to reset the internal values
	this.viewportHeight = this.getObjHeight(this.viewport);
 	this.viewportWidth  = this.getObjWidth(this.viewport);

 	if (this.viewportHeight <= 2 ||
 		this.viewportWidth <= 2)
 		this.events.triggerEvent(MDVEvent_ERROR, 'MDVMap couldn\'t get size from target map object (' + this.viewport.id + ').');
};

MDVMap.prototype.execute = function(config)  {
	// Check size
	this.resize();

	this.wrapOffset = new MDVPoint(0, 0);

	// We are looking for a configuration.
	if (config && this.config == null)
		this.config = config;
	else {
		this.events.triggerEvent(MDVEvent_ERROR, 'MDVMap couldn\'t find an appropriate configuration or already has one.')
		this.state = -1;
		return false;
	}

	// The id of the target div will the name of the map
	this.name = this.viewport.id;

	// Store maps in document property
	if (!document.mdvMaps)
		document.mdvMaps = new Array();
	document.mdvMaps['mdvMap_' + this.name] = this;

	// Set default zoom level
	if (this.config.get('defaultScale'))
		this.setZoomLevel(parseInt(this.config.get('defaultScale')));
	else
		this.setZoomLevel(3);

	// If real map centre was not set before use default coordinates
	if (!this.getCentre()) {
		var newReal = new MDVCoordinates(this.config.get('mapName'),
		  parseInt(this.config.get('xCenterReal')),
		  parseInt(this.config.get('yCenterReal')));
		this.setCentre(newReal);
	}

	// Update map values (width/height of real tile, viewport...). In order to do that we must have a centre coordinate and zoom level!
	this._updateMapValues();

	// initialise mapper
	this.mapper = document.createElement('div');
	// TODO: Perhaps we need to make the mapper id unique if we want to display multiple maps on one website.
	this.mapper.id = 'mapper';
	this.mapper.style.position = 'absolute';
	this.mapper.style.left = '0px';
	this.mapper.style.top = '0px';
	this.mapper.style.zIndex = '1';
	this.mapper.mdvMap = this;

	// Add mapper to view port
	this.viewport.appendChild(this.mapper);

	// Setup basic event handling for maps
	this.mapper.onmousemove	= MDVMap_onmousemove;
	this.mapper.onmouseover	= MDVMap_onmouseover;
	this.mapper.onmousedown 	= MDVMap_onmousedown;
	this.mapper.onmouseup 		= MDVMap_onmouseup;
	this.viewport.onmouseout	= MDVMap_onmouseout;
	this.mapper.onkeypress		= MDVMap_onkeypress;
	this.mapper.ondblclick		= MDVMap_ondblclick;
	this.mapper.oncontextmenu	= MDVMap_oncontextmenu;
	this.mapper.onmousewheel	= MDVMap_onmousewheel;

	if (window.addEventListener &&
		navigator.product && navigator.product == 'Gecko') {
		this.viewport.addEventListener('DOMMouseScroll', MDVMap_onmousewheel, false);
	}

	// Check whether we need to set a new value max fail count.
	if (this.config.get('maxFailCount')) {
		this.maxFailCount = parseInt(this.config.get('maxFailCount'));
	}

	// Populate crosshair img if available.
 	if (this.config.get('crosshair')) {
 		this.crossHairSrc.src = this.config.get('crosshair');
 	}

	// We don't want IE to handle drag events which are related to the mouse in our mapper:
	this.mapper.ondragstart = new Function([], 'var e=e?e:event;e.cancelBubble=true;e.returnValue=false;return false;');

	// Preload transparent gif placeholder
	this.transparent.src = this.config.get('transparentImg');

	// Set default cursor
	if (this.config.get('cursorIdle'))
		this.mapper.style.cursor = this.config.get('cursorIdle');

	// View the map...
	this._viewMap();

	this.renderer = document.all ? new MDVVMLRenderer(this, this.markerObjects) : new MDVSVGRenderer(this, this.markerObjects);
};

 MDVMap.prototype.destroy = function() {
 	if (this.state == -1)
 		return;
 	
 	var gLength = this.gadgets.length;
 	for (var g=0; g < gLength; g++) {
 		var gadget = this.gadgets.pop();
 		if (gadget && typeof(gadget.destroy) == "function")
	 		gadget.destroy();
 	}

 	var lLength = this.layers.length;
 	for (var l=0; l < lLength; l++) {
 		var layer = this.layers.pop();
 		if (layer)
	 		layer.destroy();
 	}

	for (var i=0; i < this.mapTiles.childNodes.length; i++) {
		if (this.mapTiles.childNodes[i].mdvMap)
			this.mapTiles.childNodes[i].mdvMap = null;
	}
 	
	if (this.mapper) {
	 	this.mapper.onmousemove		= null;
		this.mapper.onmouseover		= null;
		this.mapper.onmousedown 	= null;
		this.mapper.onmouseup 		= null;
		this.viewport.onmouseout	= null;
		this.mapper.onkeypress		= null;
		this.mapper.ondblclick		= null;
		this.mapper.oncontextmenu	= null;
		this.mapper.onmousewheel	= null;
		this.mapper.mdvMap			= null;
	}
	
	if (this.mapTiles && this.mapTiles.mdvMap)
		this.mapTiles.mdvMap 		= null; 	
	if (this.markerObjects && this.markerObjects.mdvMap)
		this.markerObjects.mdvMap	= null;
	if (this.viewport && this.viewport.mdvMap)
		this.viewport.mdvMap		= null;
	if (this.crossHair)
		this.crossHair = null;
	if (this.crossHairSrc)
		this.crossHairSrc = null;
		
	this.transparent = null;
	
	document.mdvMaps['mdvMap_' + this.name] = null;
 };

 MDVMap.prototype._updateVisibleMap = function() {
	// Reference to current zoom level.
	var zoomLevel = this.config.getZoomLevel(this.config.currentZoomLevelIndex);

	// Update map values.
	this._updateMapValues();

	// Determine start/end tiles for row and column
    var startColumn = parseInt((this.tlReal.x - parseInt(zoomLevel.get('realOffsetX')))
            / this.tileRealWidth);
    if (startColumn < 0)
            startColumn = 0;

    var endColumn   = parseInt((this.tlReal.x + this.viewportRealWidth - zoomLevel.get('realOffsetX'))
            / this.tileRealWidth);
    if (endColumn >= parseInt(zoomLevel.get('numberOfTilesX')))
            endColumn = parseInt(zoomLevel.get('numberOfTilesX')) -1;

    var startRow = parseInt((this.tlReal.y - parseInt(zoomLevel.get('realOffsetY')))
            / this.tileRealHeight);
    if (startRow < 0)
            startRow = 0;

    var endRow = parseInt((this.tlReal.y + this.viewportRealHeight - parseInt(zoomLevel.get('realOffsetY')))
            / this.tileRealHeight);
    if (endRow >= parseInt(zoomLevel.get('numberOfTilesY')))
            endRow = parseInt(zoomLevel.get('numberOfTilesY')) - 1;

	// Add 2 because we want to have one buffer tile for row and column.
    this.tileVisibleWidth  = endColumn - startColumn + 2;
    this.tileVisibleHeight = endRow - startRow + 2;
    this.startTileWidth	   = startColumn;
    this.startTileHeight   = startRow;

	// Check whether we only have one tile or less for zoom level and remove buffer tile if appropriate.
    if (zoomLevel.get('numberOfTilesX') <= 1) {
    	this.tileVisibleWidth--;
    }

    if (zoomLevel.get('numberOfTilesY') <= 1) {
    	this.tileVisibleHeight--;
    }

	var pxWidthAll          = parseInt(zoomLevel.get('tileSizeX')) * parseInt(zoomLevel.get('numberOfTilesX'));
	var pxToRealRatioWidth  = pxWidthAll / parseInt(zoomLevel.get('realWidth'));

	var pxHeightAll         = parseInt(zoomLevel.get('tileSizeY')) * parseInt(zoomLevel.get('numberOfTilesY'));
	var pxToRealRatioHeight = pxHeightAll / parseInt(zoomLevel.get('realHeight'));

	var viewportCentre = new MDVPoint((this.getCentre().x - parseInt(zoomLevel.get('realOffsetX'))) * pxToRealRatioWidth,
									  (this.getCentre().y - parseInt(zoomLevel.get('realOffsetY'))) * pxToRealRatioHeight);

	var tlViewport = new MDVPoint((this.tlReal.x - parseInt(zoomLevel.get('realOffsetX'))) * pxToRealRatioWidth,
								  (this.tlReal.y - parseInt(zoomLevel.get('realOffsetY'))) * pxToRealRatioHeight);

	var tlTile   = new MDVPoint();
		tlTile.x = (this.tlTileReal.x - parseInt(zoomLevel.get('realOffsetX'))) * pxToRealRatioWidth;
		tlTile.y = (this.tlTileReal.y - parseInt(zoomLevel.get('realOffsetY'))) * pxToRealRatioHeight;
		tlTile.x = Math.floor(tlTile.x + 0.5);
		tlTile.y = Math.floor(tlTile.y + 0.5);

	var tlOffset = new MDVPoint(tlTile.x - parseInt(tlViewport.x), tlTile.y - parseInt(tlViewport.y));

	// Add vector form the coord of upper, left intersection of viewport and upper, left tile and and the coord of the upper left tile to the mapper.
	this.mapper.style.left = tlOffset.x + 'px';
	this.mapper.style.top  = tlOffset.y + 'px';

	// Truncate
	this.getCentre().x = Math.floor(this.getCentre().x + 0.5);
	this.getCentre().y = Math.floor(this.getCentre().y + 0.5);

	this.wrapOffset = new MDVPoint(0, 0);

	fnMap_AppendHistory(this); //roccas append history

};

MDVMap.prototype._updateMapValues = function() {
	// Reference to current zoom level.
	var zoomLevel = this.config.getZoomLevel(this.config.currentZoomLevelIndex);

	if (!zoomLevel) {
		this.events.triggerEvent(MDVEvent_ERROR, 'MDVMap has an invalid zoom level.');
		return false;
	}

	var pxWidthAll          = parseInt(zoomLevel.get('tileSizeX')) * parseInt(zoomLevel.get('numberOfTilesX'));
	var pxToRealRatioWidth  = pxWidthAll / parseInt(zoomLevel.get('realWidth'));

	var pxHeightAll         = parseInt(zoomLevel.get('tileSizeY')) * parseInt(zoomLevel.get('numberOfTilesY'));
	var pxToRealRatioHeight = pxHeightAll / parseInt(zoomLevel.get('realHeight'));

	// We obtain the real sizes of a tile for a particular zoom level.
	this.tileRealWidth  = parseInt(zoomLevel.get('realWidth'))  / parseInt(zoomLevel.get('numberOfTilesX'));
	this.tileRealHeight = parseInt(zoomLevel.get('realHeight')) / parseInt(zoomLevel.get('numberOfTilesY'));

	// We obtain how many tiles are visible in view port
	this.tileCountWidth  = parseInt(this.viewportWidth)  / parseInt(zoomLevel.get('tileSizeX'));
	this.tileCountHeight = parseInt(this.viewportHeight) / parseInt(zoomLevel.get('tileSizeY'));

	// We obtain the width/height of the visible area in reality
	this.viewportRealWidth  = parseInt(this.tileRealWidth  * this.tileCountWidth);
	this.viewportRealHeight = parseInt(this.tileRealHeight * this.tileCountHeight);

	// We update the coordinates of the left/upper corner
	this.tlReal = new MDVCoordinates(this.config.get('mapName'),
									   parseInt(this.getCentre().x - (this.viewportRealWidth / 2)),
									   parseInt(this.getCentre().y - (this.viewportRealHeight / 2)));

	var tlViewport = new MDVPoint((this.tlReal.x - parseInt(zoomLevel.get('realOffsetX'))) * pxToRealRatioWidth,
								  (this.tlReal.y - parseInt(zoomLevel.get('realOffsetY'))) * pxToRealRatioHeight);

	var tlTileColumn = parseInt(tlViewport.x / parseInt(zoomLevel.get('tileSizeX')));
	var tlTileRow	 = parseInt(tlViewport.y / parseInt(zoomLevel.get('tileSizeY')));

	this.tlTileReal = new MDVCoordinates(this.config.get('mapName'),
		tlTileColumn * this.tileRealWidth + parseInt(zoomLevel.get('realOffsetX')),
		tlTileRow * this.tileRealHeight + parseInt(zoomLevel.get('realOffsetY')));
	this.tlTileReal.x = Math.floor(this.tlTileReal.x + 0.5);
	this.tlTileReal.y = Math.floor(this.tlTileReal.y + 0.5);

	return true;
};

MDVMap.prototype.getTopLeftTileReal = function() {
	return this.tlTileReal;
};

MDVMap.prototype._checkWrap = function() {
	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());

	var tileSize = new MDVPoint(zoomLevel.get('tileSizeX'), zoomLevel.get('tileSizeY'));

	var tempX = this.viewportWidth - parseInt(zoomLevel.get('numberOfTilesX')) * parseInt(zoomLevel.get('tileSizeX'));
	var tempY = this.viewportWidth - parseInt(zoomLevel.get('numberOfTilesY')) * parseInt(zoomLevel.get('tileSizeY'));

	var wrapX = zoomLevel.get('numberOfTilesX') <= 1 | tempX > 0;
	var wrapY = zoomLevel.get('numberOfTilesY') <= 1 | tempY > 0;
	
	this.offset = new MDVPoint(parseInt(this.mapper.style.left) - this.wrapOffset.x,
		parseInt(this.mapper.style.top) - this.wrapOffset.y);

	if (this.offset.x > 0) {
		if (wrapX)
			return false;

		this._applyWrapR2L();
	}

	if (this.offset.x < -(tileSize.x)) {
		if (wrapX)
			return false;

		this._applyWrapL2R();
	}

	if (this.offset.y > 0) {
		if (wrapY)
			return false;

		this._applyWrapB2T();
	}

	if (this.offset.y < -(tileSize.y)) {
		if (wrapY)
			return false;

		this._applyWrapT2B();
	}

	return true;
};

MDVMap.prototype._applyWrapT2B = function() {
	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());
	var tileSize = new MDVPoint(zoomLevel.get('tileSizeX'), zoomLevel.get('tileSizeY'));

	this.wrapOffset.y -= parseInt(tileSize.y);
	this.startTileHeight++;

	var mapTiles = this.mapTiles;
	var lastTile = parseInt(mapTiles.childNodes[this.imgCountHeight * this.imgCountWidth - 1].style.top);

	for (var x=0; x < this.imgCountWidth; x++) {
		var bottomImg = mapTiles.childNodes[0];

			bottomImg.src = this.transparent.src;
			bottomImg.style.top = (parseInt(lastTile) + parseInt(tileSize.y)) + 'px';

			mapTiles.removeChild(bottomImg);
			mapTiles.appendChild(bottomImg);
	}

	this._setMapTiles(false);

	//this.events.triggerEvent(MDVEvent_DEBUG, 'Check T2B - x: ' + (this.wrapOffset.x) + ' y: ' + (this.wrapOffset.y));
};

MDVMap.prototype._applyWrapB2T = function() {
	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());
	var tileSize = new MDVPoint(zoomLevel.get('tileSizeX'), zoomLevel.get('tileSizeY'));

	this.wrapOffset.y += parseInt(tileSize.y);
	this.startTileHeight--;

	var mapTiles = this.mapTiles;
	var topTile = parseInt(mapTiles.childNodes[0].style.top);

	for (var x=0; x < this.imgCountWidth; x++) {
		var topImg = mapTiles.childNodes[(this.imgCountHeight * this.imgCountWidth)-1];

		topImg.src 		 = this.transparent.src;
		topImg.style.top = (parseInt(topTile) - parseInt(tileSize.y)) + 'px';

		mapTiles.removeChild(topImg);
		mapTiles.insertBefore(topImg, mapTiles.childNodes[0]);
	}

	this._setMapTiles(false);

	//this.events.triggerEvent(MDVEvent_DEBUG, 'Check B2T - x: ' + (this.wrapOffset.x) + ' y: ' + (this.wrapOffset.y));
};

MDVMap.prototype._applyWrapL2R = function() {
	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());
	var tileSize = new MDVPoint(zoomLevel.get('tileSizeX'), zoomLevel.get('tileSizeY'));

	this.wrapOffset.x -= parseInt(tileSize.x);
	this.startTileWidth++;

	var mapTiles  = this.mapTiles;
	var lastTile = parseInt(mapTiles.childNodes[this.imgCountWidth-1].style.left);

	for (var y=0; y < this.imgCountHeight ; y++) {
		var firstImg  = mapTiles.childNodes[y*this.imgCountWidth];
		var beforeImg;

		if (y < (this.imgCountHeight-1)) {
			beforeImg = mapTiles.childNodes[(y+1)*this.imgCountWidth];
		} else {
			beforeImg = null;
		}

		firstImg.src 	    = this.transparent.src;
		firstImg.style.left = (parseInt(lastTile) + parseInt(tileSize.x)) + 'px';

		mapTiles.removeChild(firstImg);
		if (beforeImg) {
			mapTiles.insertBefore(firstImg, beforeImg);
		} else {
			mapTiles.appendChild(firstImg);
		}
	}

	this._setMapTiles(false);

	//this.events.triggerEvent(MDVEvent_DEBUG, 'Check L2R - x: ' + (this.wrapOffset.x) + ' y: ' + (this.wrapOffset.y));
};

MDVMap.prototype._applyWrapR2L = function() {
	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());
	var tileSize = new MDVPoint(zoomLevel.get('tileSizeX'), zoomLevel.get('tileSizeY'));

	this.wrapOffset.x += parseInt(tileSize.x);
	this.startTileWidth--;

	var mapTiles  = this.mapTiles;
	var firstTile = parseInt(mapTiles.childNodes[0].style.left);

	for (var y=0; y < this.imgCountHeight ; y++) {
		var lastImg = mapTiles.childNodes[((y+1)*this.imgCountWidth)-1];
		var nextImg = mapTiles.childNodes[y*this.imgCountWidth];

		lastImg.src 	   = this.transparent.src;
		lastImg.style.left = (firstTile - tileSize.x) + 'px';
		mapTiles.removeChild(lastImg);
		mapTiles.insertBefore(lastImg, nextImg);
	}

	this._setMapTiles(false);

	//this.events.triggerEvent(MDVEvent_DEBUG, 'Check R2L - x: ' + (this.wrapOffset.x) + ' y: ' + (this.wrapOffset.y));
};

MDVMap.prototype.update = function() {
	// We need to update all map values because zoom level or centre coordinate may have changed.
	this._updateVisibleMap();
	this._setLayers();
	this._updateMarkers();
	
	// Create transparent images
	if(this._setImages()) {
		// Load tiles...
		this._setMapTiles(true);
	} else {
		var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());

		var cX = parseInt(zoomLevel.get('realOffsetX')) + parseInt(zoomLevel.get('realWidth')) / 2;
		var cY = parseInt(zoomLevel.get('realOffsetY')) + parseInt(zoomLevel.get('realHeight')) / 2;
		var cNew = new MDVCoordinates(this.config.get('mapName'), parseInt(cX), parseInt(cY));

		this.events.triggerEvent(MDVEvent_ERROR, 'MDVMap has determined that the given coordinates are out of bounds. Setting zoom level centre.', cNew);

		this.setCentre(cNew);

		this.update();
	}

	this.events.triggerEvent(MDVEvent_MAP_INITIALISED, 'MDVMap tiles have been initialised', this);
};

MDVMap.prototype._viewMap = function() {
	// If state is -1 (not initialised) we set it to 0 (initialising).
	this.state = 0;

	// Change background colour to indicate the map is initialising. First check whether backgroundcolour has been define in the config file.
	if (this.config.get('backgroundColour'))
		this._setBackgroundColour(this.config.get('backgroundColour'));
	else
		this._setBackgroundColour('#E5E5E5');

	this.events.triggerEvent(MDVEvent_INITIALISED, 'MDVMap has been initialised', this);

	// Initialise layers.
	this._setLayers();

	this.update();
	this.state = 1;
};

MDVMap.prototype._setMapTiles = function(adjust) {
	var mapTiles = this.mapTiles;
	var zoomLevel   = this.config.getZoomLevel(this.config.getZoomLevelIndex());

	if (!(this.imgCountWidth * this.imgCountHeight == this.mapTiles.childNodes.length)) {
		this.events.triggerEvent(MDVEvent_ERROR, 'MDVMap noticed an inconsistency within the tile layer.');
		this._removeImages();
		this.update();
	}

	// Walk through every row, take every column and load the appropriate tile...
	var block    = parseInt(this.config.get('block'));
	var fileType = '.png';

	if (this.config.get('fileType')) {
		fileType = this.config.get('fileType');
	}

	for (var y=0; y<this.imgCountHeight; y++) {
		for(var x=0; x<this.imgCountWidth; x++) {
			var img  = mapTiles.childNodes[(y*this.imgCountWidth)+x];
			if (!img) {
				this.events.triggerEvent(MDVEvent_ERROR, 'Error accessing tile within DOM.');
				return false;
			}
			var pos  = new MDVPoint(x * parseInt(zoomLevel.get('tileSizeX')),
				y * parseInt(zoomLevel.get('tileSizeY')));

			var src  = this.config.get('serverURL');

			var level = this.config.getZoomLevelIndex();

			if (zoomLevel.get('zoomLevel'))
				level = zoomLevel.get('zoomLevel');

			if (this.config.get('block')) {
				var block   = parseInt(this.config.get('block'));

				var columnMin = Math.floor((this.startTileWidth + x) / block) * block;
				var columnMax = columnMin + block-1;

				var rowMin    = Math.floor((this.startTileHeight + y) /block) * block;
				var rowMax    = rowMin + block - 1;

				src += 'zoomlevel';
				src += level;
				src += '/columns' + columnMin + '-' + columnMax;
				src += '/column';
				src += (this.startTileWidth + x);
				src += '/rows' + rowMin + '-' + rowMax + '/';
				src += (this.startTileHeight + y);
				src += '_';
				src += (this.startTileWidth + x);
				src += '_';
				src += level;
				src += fileType;
			} else {
				src += 'zoomlevel';
				src += level;
				src += '/column';
				src += (this.startTileWidth + x);
				src += '/' + this.config.get('network') + '_';
				src += (this.startTileHeight + y);
				src += '_';
				src += (this.startTileWidth + x);
				src += fileType;
			}

			if (img.src != src) {
				img.src = src;
				if (adjust && !document.all)
					img.style.visibility = 'hidden';
			}

			img.style.width  = zoomLevel.get('tileSizeX');
			img.style.height = zoomLevel.get('tileSizeY');
			img.width		 = zoomLevel.get('tileSizeX');
			img.height		 = zoomLevel.get('tileSizeY');

			if (this.config.get('debug')) {
				img.alt 	= src;
				img.title 	= src;
			}

			img.failCount = 0;

			if (adjust) {
				img.style.left = pos.x + 'px';
				img.style.top  = pos.y + 'px';
			}
		}
	}

	// this.events.triggerEvent(MDVEvent_DEBUG, '--- (' + mapTiles.childNodes.length + ')');
};

MDVMap.prototype._setLayers = function() {
	if (!this.mapTiles && this.mapper) {
		// Init mdv map tiles layer
		this.mapTiles = document.createElement('div');
		this.mapTiles.className = 'mdvLayer';
		this.mapTiles.style.zIndex = '0';
		this.mapTiles.id = this.name + '_mdvMapTiles';
		this.mapTiles.style.position = 'absolute';
		this.mapTiles.style.visibility = 'visible';
		this.mapTiles.style.left = '0px';
		this.mapTiles.style.top = '0px';
		this.mapTiles.style.width = '3000px';
		this.mapTiles.style.height = '3000px';
		this.mapTiles.mdvMap = this;

		// Add mdv map tiles layer to mapper
		this.mapper.appendChild(this.mapTiles);
		this.mapTiles.append = true;
		this.events.triggerEvent(MDVEvent_DEBUG, 'MDVMap has finished initialisation of the tile div.');
	}

	// Init marker layer
	if (!this.markerObjects && this.mapper) {
		this.markerObjects = document.createElement('div');
		this.markerObjects.className = 'mdvLayer';
		this.markerObjects.style.zIndex = '1';
		this.markerObjects.id = this.name + '_mdvMarkers';
		this.markerObjects.style.position = 'absolute';
		this.markerObjects.style.visibility = 'visible';
		this.markerObjects.style.left = '0px';
		this.markerObjects.style.top = '0px';
		this.markerObjects.style.width = '3000px';
		this.markerObjects.style.height = '3000px';
		this.markerObjects.mdvMap = this;
		this.mapper.appendChild(this.markerObjects);
		this.markerObjects.append = true;
		this.events.triggerEvent(MDVEvent_DEBUG, 'MDVMap has finished initialisation of the marker objects div.');
	}

	if (!this.markerLayer) {
		this.markerLayer = this.createLayer('mdvMarkers');
		this.addLayer(this.markerLayer);
	}
};

MDVMap.prototype._setImages = function() {
	// Check whether we need to display more or less tiles within the viewport comparing the current amount of tiles against the new amount.

	if (this.tileVisibleWidth <= 0 || this.tileVisibleHeight <= 0)
		return false;

	while (this.imgCountWidth < this.tileVisibleWidth) {
		this._addColumn();
	}

	while (this.imgCountHeight < this.tileVisibleHeight) {
		this._addRow();
	}

	while (this.imgCountHeight > this.tileVisibleHeight) {
		this._removeRow();
	}

	while (this.imgCountWidth > this.tileVisibleWidth) {
		this._removeColumn();
	}

	return true;
};

MDVMap.prototype._removeImages = function () {
	// TODO: Interim solution

	var count = this.mapTiles.childNodes.length;

	for (var i = 0; i < count; i++) {
		var remove = this.mapTiles.childNodes[count - i - 1];
			remove.onmousedown = null;
			remove.onmousemove = null;
			remove.onmouseout = null;
			remove.onmouseover = null;
			remove.onmouseup = null;
			remove.onmousewheel = null;
			remove.onclick = null;
			remove.onload  = null;
			remove.onerror = null;
			if (remove.mdvMap)
			remove.mdvMap = null;
		this.mapTiles.removeChild(remove);
	};

	this.imgCountWidth = 0;
	this.imgCountHeight = 0;
};

MDVMap.prototype._addColumn = function() {
	if (this.tileVisibleWidth == 0)
		return;

	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());

	for (var i=0; i<this.imgCountHeight; i++) {
		var pos = new MDVPoint((this.imgCountWidth + 1) * parseInt(zoomLevel.get('tileSizeX')),
			i * parseInt(zoomLevel.get('tileSizeY')));

		var img = this._createImage(pos);
		this.mapTiles.appendChild(img);
	}

	this.imgCountWidth++;
};

MDVMap.prototype._removeColumn = function() {
	if (this.imgCountWidth <= 1)
		return;

	var children = this.mapTiles.childNodes.length + this.imgCountWidth - 2;

	for (var y=0; y<this.imgCountHeight; y++) {
		var index       = (this.imgCountHeight * y) + (this.imgCountWidth-1);
		var img   	    = this.mapTiles.childNodes[(children-index)];
		if (!img) {
			this.events.triggerEvent(MDVEvent_ERROR, 'Error accessing tile.');
			return false;
		}

			img.onmousedown = null;
			img.onmousemove = null;
			img.onmouseout = null;
			img.onmouseover = null;
			img.onmouseup = null;
			img.onmousewheel = null;
			img.onclick = null;
			if (img.mdvMap)
			img.mdvMap = null;
			img.onload  = null;
			img.onerror = null;
		this.mapTiles.removeChild(img);
	}

	this.imgCountWidth--;
};


MDVMap.prototype._addRow = function() {
	if (this.tileVisibleWidth == 0)
		return;

	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());
	var i;

	for (i=0; i<this.tileVisibleWidth; i++) {
		var pos = new MDVPoint(i * parseInt(zoomLevel.get('tileSizeX')),
						   this.imgCountHeight * parseInt(zoomLevel.get('tileSizeY')));

		var img = this._createImage(pos);
		this.mapTiles.appendChild(img);
		this.imgCountWidth++;
	}

	this.imgCountWidth=i;
	this.imgCountHeight++;
};

MDVMap.prototype._removeRow = function() {
	// TODO Layer removal!
	if (this.imgCountHeight <= 1)
		return;

	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());

	for (var i=0; i<this.imgCountWidth; i++) {
		var index 		= (this.imgCountHeight*this.imgCountWidth)-i-1;
		var img   		= this.mapTiles.childNodes[index];
		if (!img) {
			this.events.triggerEvent(MDVEvent_ERROR, 'MDVMap has problems while accessing tile.');
			return false;
		}
			img.onload  = null;
			img.onerror = null;
		this.mapTiles.removeChild(img);
	}

	this.imgCountHeight--;
};

MDVMap.prototype._createImage = function(pos) {
	var zoomLevel = this.config.getZoomLevel(this.config.getZoomLevelIndex());

	// Produce a new unique img tag to display a certain tile within the map grid.
	var img = document.createElement('img');
		img.src = this.transparent.src;
		img.width = parseInt(zoomLevel.get('tileSizeX'));
		img.height = parseInt(zoomLevel.get('tileSizeY'));
		img.setAttribute('style', 'position:absolute; top:'+pos.y+'px; left:'+pos.x+'px;' );
		img.style.position = 'absolute';
	    img.style.width = parseInt(zoomLevel.get('tileSizeX')); + "px";
	    img.style.height = parseInt(zoomLevel.get('tileSizeY')); + "px";
	    img.style.top = pos.y;
   	    img.style.left = pos.x;
   	    img.galleryimg = "no";
   	    img.onerror = MDVMap_imgOnError;
   	    img.onload = MDVMap_imgOnLoad;
   	    img.mdvMap = this;
   	    img.id = "t" + this.lastImageID++;
   	    img.failCount = 0;

   	    return img;
};

MDVMap.prototype.getPolylinePoints = function () {
	var xArr = new Array();
	var yArr = new Array();	
	
	for (var l = 0; l < this.layers.length; l++) {
		var coords = this.layers[l].getPolylineCoords();
		
		for (var c = 0; c < coords.length; c++) {
			var point = this.getPoint(coords[c]);
			xArr.push(point.x);
			yArr.push(point.y);
		}
	}
  		
	return [xArr, yArr];
};

MDVMap.prototype.getPolylineCoords = function () {
	var xArr = new Array();
	var yArr = new Array();	
	
	for (var l = 0; l < this.layers.length; l++) {
		var coords = this.layers[l].getPolylineCoords();
		
		for (var c = 0; c < coords.length; c++) {
			xArr.push(coords[c].x);
			yArr.push(coords[c].y);
		}
	}
  		
	return [xArr, yArr];
};

MDVMap.prototype.createLayer = function(name) {
	var retLayer = new MDVLayer(this, name);
	return retLayer;
};

MDVMap.prototype.hideToolTips = function () {
	var layers = this.getLayers();
	var ret = false;
		
	for (var l=0; l < layers.length; l++) {
		ret = layers[l].hideToolTips();
	}
	
	return ret;
};

MDVMap.prototype.createPolyline = function (coords) {
	var polyline = new MDVPolyline(this, coords);
	return polyline;
};

MDVMap.prototype.moveBySeq = function (coordSeq) {
	var update = true;
	if (this.moveJobID)
		mdvTimer.remove(this.moveJobID);

	var mSeq = this.getMoveSeq(coordSeq);
	var crosshair = null;
	
	if (arguments.length >= 2) {
		if (arguments[1])
			crosshair = arguments[1];

		if (arguments[2])
			update = arguments[2];
	}
	
	if (mSeq.length > 0 && (crosshair != null || this.config.get('crosshair'))) {
		var crs = this.crossHairSrc.src;
		
		if (crosshair)
			crs = crosshair;
		
		if (this.crossHair == null) {
			this.crossHair = this.createMarker(mSeq[0].clone(), new MDVPoint(0.5, 0.5), crs);
			this.addMarker(this.crossHair);
		} 
	}

	if (mSeq.length > 0 && update) {
		var centre = mSeq[0].clone();
		this.setCentre(centre);
		this.update();
	}
	
	thismoveJobID = mdvTimer.add(this.stepTime, this, this.move, [mSeq, 0]);
	
	return true;
};

MDVMap.prototype.getMoveSeq = function (coordSeq) {
	if (coordSeq.length <= 0)
		return null;
		
	var zoomLevel 			= this.config.getZoomLevel(this.config.currentZoomLevelIndex);
	var pxWidthAll          = parseInt(zoomLevel.get('tileSizeX')) * parseInt(zoomLevel.get('numberOfTilesX'));
	var realRatioWidthToPx  = parseInt(zoomLevel.get('realWidth')) / pxWidthAll;
	
	var steps    = new Array();
	var current  = coordSeq[0].clone();
	var distance = 0;
	var offset	 = null;
	var dest	 = null;
	var stepReal = this.stepPx * realRatioWidthToPx;
	
	for (var c = 1; c < coordSeq.length; c++) {
		dest    = coordSeq[c];
		var vec	= new MDVPoint(dest.x - current.x, dest.y - current.y);
		
		if (vec.x == 0 && vec.y == 0)
			continue;
		
			distance = Math.sqrt(Math.pow(vec.x, 2) + Math.pow(vec.y, 2));
		var norm	 = stepReal / distance;
		
			offset   = new MDVPoint(vec.x * norm, vec.y * norm);
				
		while (distance > stepReal) {
			current.x = (current.x + offset.x);
			current.y = (current.y + offset.y);
			
			steps.push(current.clone());
			
			distance  = distance - stepReal;			
		}

		if (distance > 0) {
			offset = new MDVPoint(dest.x - current.x, dest.y - current.y);		
			current.x = (current.x + offset.x);
			current.y = (current.y + offset.y);
	
			steps.push(current.clone());
		}

		distance = 0;

	}
	
	return steps;
};

MDVMap.prototype.move = function(mSeq, index) {
	if (index >= mSeq.length) {
		if (this.moveJobID)
			mdvTimer.remove(this.moveJobID);
		this.moveJobID = null;
		
		this.removeMarker(this.crossHair);
		this.crossHair = null;
		
		this.events.triggerEvent(MDVEvent_ROUTE_TRACE_FINISHED, 'MDVMap has finished moving...', mSeq);
		
		return false;
	}
		
	this.moveTo(mSeq[index]);

	index++;	
	this.moveJobID = mdvTimer.add(this.stepTime, this, this.move, [mSeq, index]);
};

MDVMap.prototype.moveTo = function (coords) {
	var centre = this.getPoint(this.getCentre());
	var target = this.getPoint(coords);
	
	if (this.crossHair)
		this.crossHair.setCoords(coords.clone());
	
	var tl = new MDVPoint(parseInt(this.mapper.style.left), parseInt(this.mapper.style.top));
	
	if (centre == null || target == null)
		return false;

	var offset = new MDVPoint(target.x - centre.x, target.y - centre.y);
	
	this.mapper.style.left = (tl.x - offset.x) + 'px';
	this.mapper.style.top  = (tl.y - offset.y) + 'px';
	
	this.setCentre(coords);
	
	this._checkWrap();
	
	return true;
};

/**
 * MDVMapConfig
 */
 function MDVMapConfig() {
 	// Array of available zoomLevels
 	this.zoomLevels = new Array();
 	this.currentZoomLevelIndex   = null;
  	this.params  = new Array();
 }
 
  MDVMapConfig.prototype.clone = function () {
  	// The clone method does only copy the basic underlying settings to a new object. Parameters which have been added at runtime are not taken into account.
  	var ret = new MDVMapConfig();

  	for (var z=0; z < this.zoomLevels.length; z++) {
  		var zl = new MDVMapConfigZoomLevel(this.zoomLevels[z].getLevel());
  			zl.setScale(this.zoomLevels[z].getScale());
		
		if (this.zoomLevels[z].get('tileSizeX'))
			zl.add('tileSizeX', this.zoomLevels[z].get('tileSizeX'));

		if (this.zoomLevels[z].get('tileSizeY'))
			zl.add('tileSizeY', this.zoomLevels[z].get('tileSizeY'));

		if (this.zoomLevels[z].get('numberOfTilesX'))
			zl.add('numberOfTilesX', this.zoomLevels[z].get('numberOfTilesX'));

		if (this.zoomLevels[z].get('numberOfTilesY'))
			zl.add('numberOfTilesY', this.zoomLevels[z].get('numberOfTilesY'));

		if (this.zoomLevels[z].get('realOffsetX'))
			zl.add('realOffsetX', this.zoomLevels[z].get('realOffsetX'));

		if (this.zoomLevels[z].get('realOffsetY'))
			zl.add('realOffsetY', this.zoomLevels[z].get('realOffsetY'));

		if (this.zoomLevels[z].get('realWidth'))
			zl.add('realWidth', this.zoomLevels[z].get('realWidth'));

		if (this.zoomLevels[z].get('realHeight'))
			zl.add('realHeight', this.zoomLevels[z].get('realHeight'));

		if (this.zoomLevels[z].get('arity'))
			zl.add('arity', this.zoomLevels[z].get('arity'));

		if (this.zoomLevels[z].get('showSTOP'))
			zl.add('showSTOP', this.zoomLevels[z].get('showSTOP'));

		if (this.zoomLevels[z].get('showPOI'))
			zl.add('showPOI', this.zoomLevels[z].get('showPOI'));

		if (this.zoomLevels[z].get('showLOCALITY'))
			zl.add('showLOCALITY', this.zoomLevels[z].get('showLOCALITY'));

		if (this.zoomLevels[z].get('block'))
			zl.add('block', this.zoomLevels[z].get('block'));

		if (this.zoomLevels[z].get('zoomLevel'))
			zl.add('zoomLevel', this.zoomLevels[z].get('zoomLevel'));

	  	ret.registerZoomLevel(zl);
  	}
  	
  	return ret;  	
  };

  MDVMapConfig.prototype.registerZoomLevel = function(zoomLevelObj) {
	this.zoomLevels[zoomLevelObj.level]	= zoomLevelObj;
  };

  MDVMapConfig.prototype.getZoomLevels = function() {
	return this.zoomLevels;
  };

  MDVMapConfig.prototype.getZoomLevel = function(index) {
  	if (this.zoomLevels[index])
		return this.zoomLevels[index];
	else
		return null;
  };

  MDVMapConfig.prototype.add = function(key, value) {
  	this.params[key] = value;
  };

  MDVMapConfig.prototype.get = function(key) {
  	return this.params[key];
  };

  MDVMapConfig.prototype.setZoomLevelIndex = function(zoomLevelIndex) {
  	this.currentZoomLevelIndex = zoomLevelIndex;
  };

  MDVMapConfig.prototype.getZoomLevelIndex = function() {
  	return this.currentZoomLevelIndex;
  };

 /**
  * MDVMapConfigZoomLevel
  * @param {String} level
  */
  function MDVMapConfigZoomLevel(level) {
  	this.level   = level;
 	this.scale   = null;
  	this.params  = new Array();
  }

  MDVMapConfigZoomLevel.prototype.add = function(key, value) {
  	this.params[key] = value;
  };

  MDVMapConfigZoomLevel.prototype.get = function(key) {
  	return this.params[key];
  };

  MDVMapConfigZoomLevel.prototype.setScale = function(scale) {
  	this.scale = scale;
  };

  MDVMapConfigZoomLevel.prototype.getScale = function() {
  	return this.scale;
  };

  MDVMapConfigZoomLevel.prototype.getLevel = function() {
  	return this.level;
  };

/**
 * MDVEvents
 */
 function MDVEvents() {
 	this.events = [];
 	this.lastEventID = 0;
 }

 MDVEvents.prototype.addEventID = function (id) {
 	if(!this.events[id]){
 		this.events[id] =[];
 	}
 };

 MDVEvents.prototype.registerEvent = function (id, obj, func) {
	if (this.events[id]) {
	 	this.events[id].push([ obj, func ]);
	}
 };

 MDVEvents.prototype.deregisterEvent = function (id, obj, func) {
 	var retVal = false;

 	if(!this.events[id])
 		return false;

 	for (var i=0; i<this.events[id].length; i++) {

 		if (this.events[id][i][0] == obj &&
 			this.events[id][i][1] == func) {

 			this.events[id].splice(i, 1);
 			retVal = true;
 		}
 	}

 	return retVal;
 };

 MDVEvents.prototype.triggerEvent = function (id) {

    if (!this.events[id]) {
        return false;
    }

    var args = new Array();
    for (var i=0; i<arguments.length; i++)
		args[args.length] = arguments[i];

	// If there is a textarea with id 'MDVMap_Console' we are going to populate it as soon as a event got triggered.
    if (document.getElementById &&
    	document.getElementById('MDVMap_Console') &&
    	args.length >= 2) {
    	var curDate = new Date();

		// Assemble log string...
    	var dateHr  = curDate.getHours();
    	var dateMin = curDate.getMinutes();
    	var dateSec = curDate.getSeconds();

    	if (dateHr  <= 9)
    		dateHr  = '0' + dateHr;
    	if (dateMin <= 9)
    		dateMin = '0' + dateMin;
    	if (dateSec <= 9)
    		dateSec = '0' + dateSec;

    	var dateStr = dateHr  + ':' +
    				  dateMin + ':' +
    				  dateSec;

    	document.getElementById('MDVMap_Console').value = dateStr + ' ' + args[1] + ' (id: ' + id + ')' + '\n' + document.getElementById('MDVMap_Console').value;
    }

    for (var i=0; i<this.events[id].length; i++)
    	this.events[id][i][1].apply( this.events[id][i][0], arguments );

    return true;

 };

MDVMap_onmousemove = function(e) {
	e = e ? e : window.event;
	var ret = true;

	if (e.button==2)
		this.mdvMap.events.triggerEvent(MDVEvent_CONTEXT_MENU);

	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if(ret) {
			if (!this.mdvMap.gadgets[g].onmousemove(e))
				ret = false;
		}
	}

	return ret;
}

MDVMap_onmouseover = function(e) {
	e = e ? e : window.event;
	var ret = true;

	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if (!this.mdvMap.gadgets[g].onmouseover(e))
			ret = false;
	}
	
	return ret;
};

MDVMap_onmousedown = function(e) {
	e = e ? e : window.event;
	var ret = true;

	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if (!this.mdvMap.gadgets[g].onmousedown(e))
			ret = false;
	}

	return ret;
};

MDVMap_onmouseup = function(e) {
	e = e ? e : window.event;

	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if (!this.mdvMap.gadgets[g].onmouseup(e))
			return false;
	}

	return true;
};

MDVMap_onmouseout = function(e) {
	e = e ? e : window.event;
	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if (!this.mdvMap.gadgets[g].onmouseout(e))
			return false;
	}

	return true;
};

MDVMap_onkeypress = function(e) {
	e = e ? e : window.event;

	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if (!this.mdvMap.gadgets[g].onkeypress(e))
			return false;
	}

	return true;
};

MDVMap_ondblclick = function(e) {
	e = e ? e : window.event;

	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if (!this.mdvMap.gadgets[g].ondblclick(e))
			return false;
	}

	return true;
};

MDVMap_oncontextmenu = function(e) {
	e = e ? e : window.event;

	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if (!this.mdvMap.gadgets[g].oncontextmenu(e))
			return false;
	}

	return true;
};

MDVMap_onmousewheel = function(e) {
	e = e ? e : window.event;

	// Bubble event to registered gadgets
	for(var g=0; g <this.mdvMap.gadgets.length; g++) {
		if (!this.mdvMap.gadgets[g].onmousewheel(e))
			return false;
	}

	return true;
};

MDVMap_imgOnError = function() {
	if (this.mdvMap) {
		this.mdvMap.events.triggerEvent(MDVEvent_WARNING, 'MDVMap couldn\'t load tile ' + this.id + ' (failCount: ' + this.failCount + ')');
		this.failCount++
		// Save failed URL.
		var failSrc = this.src;
		this.src = this.mdvMap.transparent.src;

		if (this.failCount <= this.mdvMap.maxFailCount) {
			// If fail count was not exceeded try again...
			this.src = failSrc;
		} else {
			// If fail count was exceeded set transperent
			this.src = this.mdvMap.transparent.src;
		}
	}
};

MDVMap_imgOnLoad = function() {
	this.style.visibility = 'visible';
	if (this.failCount > 0 && this.src != this.mdvMap.transparent.src)
		this.mdvMap.events.triggerEvent(MDVEvent_WARNING, 'MDVMap recovered tile ' + this.id + ' (failCount: ' + this.failCount + ')');
};

// Coordinates classes for real and pixel

/**
 * MDVCoordinates
 */
 function MDVCoordinates(mapName, x, y) {
 	this.x = x;
 	this.y = y; 	

 	if (typeof x == 'string')
	 	this.x = parseInt(this.x);

 	if (typeof y == 'string')
	 	this.y = parseInt(this.y);
	 	
 	this.mapName = mapName;
 }

 MDVCoordinates.prototype.toString = function() {
 	return this.mapName + ', x: ' + this.x + ', y: ' + this.y;
 };

 MDVCoordinates.prototype.equals = function(coords) {
	 if (!coords || !coords.x || !coords.y)
	 	return false;

	 	var x = this.x;
	 	var y = this.y;

	 	var targetX = coords.x;
	 	var targetY = coords.y;

	 	if (x % 1 > 0)
	 		x = parseInt(Math.floor(x + 0.5));
	 	if (y % 1 > 0)
	 		y = parseInt(Math.floor(y + 0.5));

	 	if (targetX % 1 > 0)
	 		targetX = parseInt(Math.floor(targetX + 0.5));
	 	if (targetY % 1 > 0)
	 		targetY = parseInt(Math.floor(targetY + 0.5));

	 return (x == targetX && y == targetY && this.mapName == coords.mapName);
 };

 MDVCoordinates.prototype.clone = function() {
	 return new MDVCoordinates(this.mapName, this.x, this.y);
 };

/**
 * MDVPoint
 */
 function MDVPoint(x, y) {
 	this.x = x;
 	this.y = y;
 	
 	this.x = x;
 	this.y = y; 	

 	if (typeof x == 'string')
	 	this.x = parseInt(this.x);

 	if (typeof y == 'string')
	 	this.y = parseInt(this.y); 	
 }

 MDVPoint.prototype.toString = function() {
 	return 'x: ' + this.x + ', y: ' + this.y;
 };

 MDVPoint.prototype.equals = function(point) {
	 if (!point || !point.x || !point.y)
	 	return false;

	 	var x = this.x;
	 	var y = this.y;

	 	var targetX = point.x;
	 	var targetY = point.y;

	 	if (x % 1 > 0)
	 		x = parseInt(Math.floor(x + 0.5));
	 	if (y % 1 > 0)
	 		y = parseInt(Math.floor(y + 0.5));

	 	if (targetX % 1 > 0)
	 		targetX = parseInt(Math.floor(targetX + 0.5));
	 	if (targetY % 1 > 0)
	 		targetY = parseInt(Math.floor(targetY + 0.5));

	 return (x == targetX && y == targetY);
 };

 MDVPoint.prototype.clone = function() {
	 return new MDVPoint(this.x, this.y);
 };

 /**
  * MDVMarker
  * @param {string} imgSrc
  */
  function MDVMarker(mdvMap, coords, imgSrc, factor) {
  	if (!mdvMap)
  		return;

  	this.mdvMap = mdvMap;
  	this.img = new Image();
  	this.img.marker = this;
  	this.img.mdvMap = mdvMap;
  	this.img.onload	= MDVMarker_onload;
  	this.imgSrc = imgSrc;
  	//this.img.src = imgSrc;
  	this.marker = this;
  	this.id = mdvMap.lastMarkerID++;
  	this.coords = coords;
  	this.toolTip = null;
	this.objectId = { type: 'coord', desc: '', id: this.getCoords().x + ':' + this.getCoords().y + ':' + this.getCoords().mapName , omc: '', marker: this };
  	// Appended?
  	this.append = false;

	if (typeof factor == 'object' && factor && factor.x >= 0 && factor.y >= 0) {
		this.xFactor = factor.x;
		this.yFactor = factor.y;
	} else {
	  	this.xFactor = factor;
	  	this.yFactor = 1;
	} 	

  }
  
  MDVMarker.prototype.intersects = function (mrkr) {
  	if (mrkr == null) 
  		return false; 		
  		
  	var recThis = this.getRectangle();
  	var recMrkr = mrkr.getRectangle();
  	
	return recMrkr.width > 0 && recMrkr.height > 0 && recThis.width > 0 && recThis.height > 0
		&& recMrkr.x < recThis.x + recThis.width && recMrkr.x + recMrkr.width > recThis.x
		&& recMrkr.y < recThis.y + recThis.height && recMrkr.y + recMrkr.height > recThis.y; 		
  };
  
  MDVMarker.prototype.getRectangle = function () {
  	var point = this.mdvMap.getPoint(this.getCoords());
  	
  	var width  = this.img.width;
  	var height = this.img.height;
  	
	var xFactor = this.getXFactor();
	var yFactor = this.getYFactor();
	
	var upperLeft  = new MDVPoint(point.x - width  * xFactor,
							      point.y - height * yFactor);
							   
	return { x: upperLeft.x, y: upperLeft.y, width: width, height: height };
  }
  
  MDVMarker.prototype.update = function () {
	var point = this.mdvMap.getPoint(this.getCoords());
	var ttUpdate = true;
	
	if (!point) {
		this.mdvMap.events.triggerEvent(MDVEvent_ERROR, 'MDVMap wasn\'t able to get px coordinates for marker.');
		return false;
	}
	
	if (arguments.length > 0)
		ttUpdate = arguments[0];

	var width  = this.img.width;
	var height = this.img.height;

	var xFactor = this.getXFactor();
	var yFactor = this.getYFactor();

	this.img.style.position 	= 'absolute';
	this.img.style.left = (point.x - width * xFactor) + 'px';
	this.img.style.top  = (point.y - height * yFactor) + 'px';
	if(typeof(this.zIndex) != 'undefined') // shbgr added if (IE8)
		this.img.style.zIndex = this.zIndex;
	
	if (this.toolTip && this.layer && !this.append)
		this.toolTip.setZIndex(this.layer.zIndex+10)

	if (this.toolTip && ttUpdate)
		this.toolTip.update();
	
	return true;  	
  };
  
  MDVMarker.prototype.destroy = function () {
	  	// External
	  	this.img.marker = null;
	  	this.img.mdvMap = null;

  		if (this.toolTip)
  			this.toolTip.destroy();
  			
  		if (this.marker.layer)
  			this.marker.layer = null;

	  	this.toolTip = null;
	  	this.objectId = null;
	  	this.coords = null;

	  	this.marker = null;

  		// DOM
	  	this.img.onload = null;
	  	this.img = null;
  };

  MDVMarker.prototype.getCoords = function() {
  	if (this.coords && this.coords.x > 0 && this.coords.y > 0) {
  		return this.coords;
  	}

  	return null;
  };

  MDVMarker.prototype.setCoords = function(coords) {
  	this.coords = coords;

  	this.update();
  	
  	if (this.toolTip) {
  		this.toolTip.setCoords(this.getCoords());
  		this.toolTip.update();
  	}
  };

  MDVMarker.prototype.getXFactor = function() {
  	return this.xFactor;
  };
  
  MDVMarker.prototype.getYFactor = function() {
  	return this.yFactor;
  };

  MDVMarker.prototype.setTitle = function(title) {
  	this.img.title = title;
  	this.img.alt = title;
  };

  MDVMarker.prototype.setToolTip = function(tooltip) {
  	if (!tooltip)
  		return false;
  		
  	var update = true;

  	if (arguments.length == 2)
  		update = arguments[1];

  	var ttCoords = new MDVCoordinates(this.coords.mapName,
  		this.coords.x,
  		this.coords.y);

  	this.toolTip = tooltip;
  	this.toolTip.setParent(this);
  	this.img.toolTip = tooltip;
  	this.toolTip.setCoords(ttCoords);
	if (update)
	  	this.toolTip.update();

  	this.img.onmouseover = MDVMap_displayToolTip;
  	this.img.onmouseout	 = MDVMap_hideToolTip;
  	this.img.onclick 	 = MDVMap_onclick;
	this.img.onmousemove = function(e) { // added, disables help
		if(typeof(window.event) != "undefined" && window.event != null)
			window.event.cancelBubble = true;
		return false;
	}; 

  	return true;
  };
  
  MDVMarker.prototype.setObjectId = function (objectId) {
  		this.objectId = objectId;
  };

  MDVMarker.prototype.getObjectId = function () {
  		return this.objectId;
  };
  
  MDVMarker.prototype.getImage = function() {
  		return this.img;
  };

  MDVMarker.prototype.checkToolTipExtents = function() {
  		if (!this.toolTip)
  			return false;

		var currentPoint = new MDVPoint(parseInt(this.toolTip.div.style.left), parseInt(this.toolTip.div.style.top));
		var point  = this.mdvMap.getPoint(this.toolTip.coords);

		if (currentPoint != point)
			return true;

   		var width  = this.mdvMap.getObjWidth(this.toolTip.div);
   		var height = this.mdvMap.getObjHeight(this.toolTip.div);

		var offset = new MDVPoint(parseInt(this.mdvMap.mapper.style.left), parseInt(this.mdvMap.mapper.style.top));

		// Check whether we need to display the tool tip left adjacent.
   		if (point.x + offset.x + (width+1.1) > this.mdvMap.viewportWidth) {
			return true;
		}

		// Check whether we need to display the tool tip top adjacent.
		if (point.y + offset.y + (height*1.1) > this.mdvMap.viewportHeight) {
			return true;
		}

		return false;
  };
  
  function MDVMarker_onload (e) {
   		e = e ? e : window.event;   	

  		// We need to wait for the onload event in Firefox as it doesn't provide proper values for img width and height, if we don't wait.
		if (this.marker) {
			this.marker.update();
			if (!this.marker.append && this.marker.layer) {
				this.mdvMap.markerObjects.appendChild(this);
				this.marker.append = true;
			}
		}
  }

  /**
   * MDVToolTip
   * @param {object} mdvMap
   */
   function MDVToolTip(mdvMap, innerHTML) {
   		if (!mdvMap)
   			return;

   		this.mdvMap 	  = mdvMap;
   		this.id 		  = this.mdvMap.lastToolTipID++;
   		this.hideJob	  = null;
   		this.displayJob	  = null;
   		this.coords		  = null;
   		this.mousePos	  = null;
   		this.div 		  = null;
   		this.visibility   = null;
   		this.append		  = false;
   		this.parentMarker = null;
   		this.slidable	  = true;
   		this.dynamicData	  = null;

		this.div = document.createElement('div');
   		this.div.onmouseover = MDVToolTip_CancelHiding;
   		this.div.onmouseout = MDVToolTip_TriggerHiding;
 		this.div.onmousedown = MDVToolTip_OnMouseDown;
		this.div.className = 'MDVToolTip';
		this.div.innerHTML = innerHTML;
		this.div.toolTip = this;
		this.setVisibility(false);
   }
   
   MDVToolTip.prototype.destroy = function() {
   		// External
   		this.mdvMap = null;   		
   		this.parentMarker = null;

   		// DOM
   		this.div.onmouseover = null;
   		this.div.onmouseout = null;
   		this.div.onmousedown = null;
   		this.div.toolTip = null;
   		this.div = null;
   };

   MDVToolTip.prototype.setVisibility = function (visibility) {
   		var visb = visibility ? 'visible' : 'hidden';
   		this.visibility = visibility;
   		
   		if (this.div)
	   		this.div.style.visibility = visb;
	   	
	   	if (this.mdvMap.events) {
			try{
				this.mdvMap.events.triggerEvent(MDVEvent_TOOLTIP, this);	
			}
			catch(e) {
			}
		}
   };
   
   MDVToolTip.prototype.setMousePosition = function (pos) {
   		this.mousePos = pos;
   };
   
   MDVToolTip.prototype.getMousePosition = function () {
   		return this.mousePos;
   };
   
   MDVToolTip.prototype.triggerDisplaying = function() {
   		// Perhaps we need to store the timeout information in config.
   		var timeout = 500;   		
   		this.displayJob = mdvTimer.add(timeout, this, this.setVisibility, [true]);
		if(this.dynamicData != null) {	// roccas hook for dm
			this.dynamicData.method(this, this.dynamicData.parameters);
		}
   };
   
//roccas DM
function roccas_DMToolTipRequest(pmToolTip, pmParameters) {
	var params = { stopID: pmParameters.stopId };
	params = $H(params).toQueryString();
//	alert(params);
	var _ajax = new Ajax.Request('/ib/site/tools/Interface_AJAX_DM.php', { method: 'get', asynchronous: false, parameters: params});

//	pmToolTip.div.innerHTML = _ajax.transport.responseText.length;

	var mot = new Array();
	mot[1] = "u_bahn";
	mot[2] = "s_bahn";
	mot[3] = "bus";
	mot[4] = "tram";
	mot[6] = "zug";
	mot[8] = "taxi";
	mot[16] = "ast";
	mot[20] = "bus";

	var name = _ajax.transport.responseXML.getElementsByTagName('odvNameElem');
//	alert(_ajax.transport.responseText);
	for (var i=0; i < name.length; i++) {
		var content = "<div style=\"padding:3px 5px 5px 5px\">Haltestelle " + name[i].firstChild.nodeValue + "</div>";
	}
	
	var departures = _ajax.transport.responseXML.getElementsByTagName('itdDeparture');
	if(departures.length == 0) {
		content += "<div style=\"padding:3px\">-- keine aktuellen Abfahrten --</div>";
	}
	content += "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"300\">";
	for (var i=0; i < Math.min(departures.length, 5); i++) {
		var dep = departures[i];
		var t = dep.getElementsByTagName('itdTime');
		var line = dep.getElementsByTagName('itdServingLine');
		if(i % 2 == 0) {
			var bgc = "E3E8EE";
		}
		else {
			var bgc = "FFFFFF";
		}
		var min = "0" + t[0].getAttribute("minute");
		min = min.substr(min.length-2);
		content += "<tr style=\"background-color:#" + bgc + "\">";
		content += "<td style=\"padding:1px 5px 1px 5px\">" + t[0].getAttribute("hour") + ":" + min + "</td>";
		content += "<td style=\"padding:1px 0px\"><img src=\"/ib/site/elements/efa/means/" + mot[line[0].getAttribute("code")] + ".gif\"></td>";
		content += "<td style=\"padding:1px 5px 1px 5px\">" + line[0].getAttribute("symbol") + "</td>";
		content += "<td style=\"padding:1px 5px 1px 0px\">" + line[0].getAttribute("direction") + "</td>";
		content += "</tr>";
	}
	content += "</table>";
	content += "<div class=\"button_context\" style=\"padding:10px 5px 3px 5px;\"><table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"290\"><tr><td>";
	content += "Klicken Sie, um diese Haltestelle auszuw&auml;hlen.</td></tr></table></div>";
	if(typeof(pmToolTip) == "undefined" || pmToolTip == null)
		return;
	if(typeof(pmToolTip.div) == "undefined" || pmToolTip.div == null)
		return;
	pmToolTip.div.innerHTML = content;
	pmToolTip.update();
}
//end roccas DM
   
   MDVToolTip.prototype.cancelDisplaying = function () {
   		if (this.displayJob != null) {
   			if (mdvTimer.remove(this.displayJob)) {
   				this.displayJob = null;
   				return true;
   			}
   		}
   		
   		return false;
   };

   MDVToolTip.prototype.triggerHiding = function () {
   		var timeout = 100;
   		if (this.mdvMap.config.get('toolTimeOut'))
   			timeout = parseInt(this.mdvMap.config.get('toolTimeOut'));

   		this.hideJob = mdvTimer.add(timeout, this, this.setVisibility, [false]);
   };

   MDVToolTip.prototype.cancelHiding = function () {
   		if (this.hideJob != null) {
   			if (mdvTimer.remove(this.hideJob)) {
	   			this.hideJob = null;
	   			return true;
   			}
   		}

   		return false;
   };

   MDVToolTip.prototype.setInnerHTML = function (innerHTML) {
   		this.div.innerHTML = innerHTML;
   };

   MDVToolTip.prototype.getInnerHTML = function () {
   		return this.div.innerHTML;
   };

   MDVToolTip.prototype.invertVisibility = function() {
   		if (this.visibility) {
   			this.setVisibility(false);
   		} else {
   			this.setVisibility(true);
   		}
   };
   
   MDVToolTip.prototype.isVisible = function () {
   		return this.visibility;
   };

   MDVToolTip.prototype.setCoords = function (coords)  {
   		if (!coords || !coords.x || !coords.y)
   			return false;

   		this.coords = coords;
		// TODO: Why update?
   		//this.update();

   		return true;
   };

   MDVToolTip.prototype.getCoords = function () {
   		return this.coords;
   };

   MDVToolTip.prototype.setParent = function(parent) {
   		this.parentMarker = parent;
   };

   MDVToolTip.prototype.getParent = function() {
   		return this.parentMarker;
   };

   MDVToolTip.prototype.update = function () {
		var point  = this.mdvMap.getPoint(this.coords);
		// Perhaps wee need to store the tooltip offset value in config.
		var toolTipOffset = 4;

   		if(!point) {
 			this.mdvMap.events.triggerEvent(MDVEvent_ERROR, 'MDVMap wasn\'t able to get px coordinates for marker.');
	   		return false;
   		}

   		if (!this.append) {
   			this.mdvMap.markerObjects.appendChild(this.div);
   			this.append = true;
   		}

   		var parent = this.getParent();
		var xFactor = 0;
		var iwidth = 0;
		var iheight = 0;
		
		if (parent.img) {
			xFactor = parent.img.width * parent.getXFactor();			
			iwidth = parent.img.width;
			iheight = parent.img.height;
		}
		
   		this.div.style.position = 'absolute';
   		this.div.style.left     = (point.x + xFactor + toolTipOffset) + 'px';
   		this.div.style.top 		= (point.y + toolTipOffset) + 'px';

   		var width  = this.mdvMap.getObjWidth(this.div);
   		var height = this.mdvMap.getObjHeight(this.div);
		var offset = new MDVPoint(parseInt(this.mdvMap.mapper.style.left), parseInt(this.mdvMap.mapper.style.top));
		
		var restX = 140;// beruecksichtigt navbar
		var restY = 290;// beruecksichtigt navbar

		// Check whether we need to display the tool tip left adjacent.
   		if (point.x + offset.x + width + xFactor + toolTipOffset > (this.mdvMap.viewportWidth - toolTipOffset)) {
   			this.div.style.left = (point.x - width - iwidth + xFactor - toolTipOffset) + 'px';
   			
   			// It might be the case that we do not have that much space to the left hand side to accommodate the tool tip.
   			if (point.x - width - iwidth + xFactor - (toolTipOffset*2) + offset.x <= restX) { // beruecksichtigt navbar
				this.div.style.left= (this.mdvMap.viewportWidth - offset.x - width - toolTipOffset) + 'px'; // positioniert am rechten rand
   			}
		}

		// Check whether we need to display the tool tip top adjacent.
		if (point.y + offset.y + height + toolTipOffset > (this.mdvMap.viewportHeight - 2)) {

			var deltaTop = (point.y - height - iheight - (toolTipOffset*2) + offset.y);
			// Value is positiv if tooltip is going to be truncated at the top.
			if (deltaTop > 0)
				deltaTop = 0;

			this.div.style.top = (point.y - height - iheight - toolTipOffset - deltaTop) + 'px';
		}

   		if (parseInt(this.div.style.top) + offset.y - toolTipOffset < restY && offset.x + point.x < restX) { // beruecksichtigt navbar
			this.div.style.left = (restX - offset.x + toolTipOffset) + "px";// beruecksichtigt navbar
		}

   		return true;
   };      
   MDVToolTip.prototype.setZIndex = function(zIndex) {
   		this.div.style.zIndex  = zIndex;
   }
   
   MDVToolTip.prototype.isSlidable = function () {
   		return this.slidable;
   };
   
   MDVToolTip.prototype.setSlidable = function (slidable) {
   		this.slidable = slidable;
   }

   function MDVMap_displayToolTip(e) {
   		e = e ? e : window.event; 
   		
   		var element = this.marker || this;
   		
   		if (element) {
   			if (element.toolTip)
	   			element.toolTip.cancelHiding();
	   			
	   		this.mdvMap.hideToolTips();
	   			
		    var mX = e.pageX || (e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft));
		    var mY = e.pageY || (e.clientY + (document.documentElement.scrollTop || document.body.scrollTop));
		    var offsetL = 0; var offsetT = 0;
		    var obj = e.target || e.srcElement;
		
		    while(obj) {
		    	if (obj.offsetLeft && obj.offsetTop) {
			    	offsetL += obj.offsetLeft;
			    	offsetT += obj.offsetTop;
		    	}
		    	obj = obj.offsetParent;
		    }
		
		    mX = mX - (offsetL);
		    mY = mY - (offsetT);
			var mousePosition = new MDVPoint(mX, mY);
			
			// Inject Mouse Position
			if (!this.marker) {
				element.toolTip.setMousePosition(mousePosition);
			}
			
   			if (element.checkToolTipExtents) {
   				if (element.checkToolTipExtents())
	   				element.toolTip.update();   				
   			} else
	   			element.toolTip.update();
//alert(element.objectId.type);
//{ type: pinType, desc: pinName, id: pinId, omc: pinOmc, marker: mapPin };
	   		element.toolTip.triggerDisplaying();
   		}
   };

   function MDVMap_hideToolTip(e) {
   		e = e ? e : window.event;   	
   		
   		var element = this.marker || this;
   		if (element && element.toolTip) {
   			element.toolTip.cancelDisplaying();
   			element.toolTip.triggerHiding();
   		}   		
   };

   function MDVToolTip_CancelHiding(e) {
   		e = e ? e : window.event;
   		if (this.toolTip)
	   		this.toolTip.cancelHiding();
   }

   function MDVToolTip_TriggerHiding(e) {
   		e = e ? e : window.event;
   		if (this.toolTip)
	   		this.toolTip.triggerHiding();
   }
   
   function MDVToolTip_OnMouseDown(e) {
   		e = e ? e : window.event;
 		
   		if (!this.toolTip.isSlidable()) {
			e.cancelBubble = true;
			e.returnValue = false;
			
			if (e.stopPropogation)
			    e.stopPropogation();
			
			if (e.preventDefault)
			    e.preventDefault();
	
	   		return false; 
   		}
   		
   		return true;
   }

 /**
  * MDVLayer
  * @param {string} layerName
  */
  function MDVLayer(mdvMap, layerName) {
	  	if(!mdvMap || layerName == '')
			return;

		// Layer Name
		this.name = layerName;

		// MDVMap Reference
		this.mdvMap = mdvMap;

		// zIndex
		this.zIndex = 0;

		// Layer Id
		this.id = mdvMap.lastLayerID++;

		// Indicates whether layer is visible or not.
	  	this.visible = true;

	  	// Indicates whether layer has been added to the map
	  	this.append = false;

	  	// Markers
	  	this.markers = new Array();

	  	// Polylines
	  	this.polylines = new Array();
  }
  
  MDVLayer.prototype.getPolylineCoords = function () {
  	var arr = new Array();
  	
	for (var i = 0; i < this.polylines.length; i++) {
		for (var c = 0; c < this.polylines[i].coords.length; c++) {
			arr.push(this.polylines[i].coords[c]);
		}
	}
	
	return arr;
  };
  
  MDVLayer.prototype.destroy = function() {
  		// DOM
  		var pLength = this.polylines.length;
  		for (var p=0; p < pLength; p++) {
  			var polyline = this.polylines.pop();
  			polyline.destroy();
  		}
  		// External
  		this.mdvMap = null;
  };
  
  MDVLayer.prototype.addPolyline = function(polyline) {
  		if (!polyline)
  			return false;
  			
  		var update = true;
  			
  		for (var p = 0; p < this.polylines.length; p++) {
  			if (this.polylines[p].id == polyline.id)
  				return false;
  		}
  		
	  	if (arguments.length == 2)
	  		update = arguments[1];
  		
  		this.polylines.push(polyline);  		
  		if (update) {
  			this.update();
  		}
  			
  		return true;
  };
  
  MDVLayer.prototype.hideToolTips = function () {
  		var ret = false;
	  	for (var m=0; m < this.markers.length; m++) {
	  		if (this.markers[m].toolTip) {
		  		mdvTimer.remove(this.markers[m].toolTip.job);
		  		if (this.markers[m].toolTip.isVisible()) {
			  		this.markers[m].toolTip.setVisibility(false);
		  			ret = true;
		  		}
	  		}
	  	}

  		for (var p = 0; p < this.polylines.length; p++) {
  			if (this.polylines[p].toolTip) {
  				mdvTimer.remove(this.polylines[p].toolTip.job);
  				if (this.polylines[p].toolTip.isVisible())
  					this.polylines[p].toolTip.setVisibility(false);
  					ret = true;
  			}
  		}

	  	return ret;
  };

  MDVLayer.prototype.setVisibility = function(visibility) {
  		if (this.visible != visibility) {
	  		this.visible = visibility;

		  	for (var m=0; m < this.markers.length; m++) {
		  		var value = this.visible ? 'visible' : 'hidden';
		  		this.markers[m].img.style.visibility = value;
		  	}
  		}
  };

  MDVLayer.prototype.update = function() {
	  	for (var m=0; m < this.markers.length; m++) {
	  		this.markers[m].update();
	  	}
	  	
	  	this.mdvMap.events.triggerEvent(MDVEvent_LAYER_UPDATED, 'MDVMap has updated layer (' + this.name + ')', this);
	  	
		if (this.mdvMap.renderer)
			this.mdvMap.renderer.update();

	  	for (var p=0; p < this.polylines.length; p++) {
	  		this.mdvMap.renderer.drawPolyline(this.polylines[p]);
	  	}
  };

  MDVLayer.prototype.addMarker = function(marker) {
  		var update = true;
	  	for (var m=0; m < this.markers.length; m++) {
	  		if (this.markers[m].id == marker.id) {
	  			return false;
	  		}
	  	}
	  	
	  	if (arguments.length == 2)
	  		update = arguments[1];

	  	marker.layer = this;

		this.markers.push(marker);		
		marker.img.src = marker.imgSrc;

		return true;
  };

  MDVLayer.prototype.removeMarker = function(marker) {
  		var tempArr = new Array();
  		var update = false;
  		var markersLength = this.markers.length;

	  	for (var m=0; m < markersLength; m++) {
	  		var temp = this.markers.shift();

  			if (temp.id == marker.id) {
  				this.markers = tempArr.concat(this.markers);
  				
  				this.mdvMap.markerObjects.removeChild(temp.img);

  				if (temp.toolTip)
  					this.mdvMap.markerObjects.removeChild(temp.toolTip.div);

  				temp.destroy();

  				update = true;
  				break;
  			}

	  		tempArr.push(temp);
	  	}

	  	return update;
  }
  
  MDVLayer.prototype.getMarkers = function() {
  	return this.markers;
  };

  MDVLayer.prototype.removeAll = function() {
  		var markers = this.markers;

		markersLength = markers.length;

	  	while(markers.length > 0) {
	  		var temp = markers.shift();

			if (temp.img.parentNode == this.mdvMap.markerObjects)
				this.mdvMap.markerObjects.removeChild(temp.img);			
				
			if (temp.toolTip && temp.toolTip.div.parentNode == this.mdvMap.markerObjects)
				this.mdvMap.markerObjects.removeChild(temp.toolTip.div);

			temp.destroy();
  	  	}

	  	return true;
  };

  MDVLayer.prototype.setZIndex = function(zIndex) {
  		this.zIndex = zIndex * this.id;
  };


  /**
   * MDVTimer
   */
   function MDVTimer() {
   		this.jobs = new Array();
   }

   MDVTimer.prototype.add = function (timeout, target, func, arg) {
   		var pos = this.jobs.length;

   		for (var i=0; i < this.jobs.length; i++) {
   			if (this.jobs[i] == null) {
   				pos = i;
   				break;
   			}
   		}

   		var id = window.setTimeout('MDVTimer_execute(' + pos + ')', timeout);
   		this.jobs[pos] = new MDVTimerJob(id, target, func, arg);
   		
   		return pos;
   };

   MDVTimer.prototype.remove = function (pos) {
   		if (this.jobs[pos] != null) {
	   		window.clearTimeout(this.jobs[pos].id);
   			this.jobs[pos] = null;
	   		return true;
   		}
   		
   		return false;
   };

   var mdvTimer = new MDVTimer();

   /**
    * MDVTimerJob
    * @param {int} id
    * @param {obj} target
    * @param {function} func
    * @param {array} arg
    */
    function MDVTimerJob(id, target, func, arg) {
    	this.id 	= id;
    	this.target = target;
    	this.func 	= func;
    	this.arg	= arg;
    }

    function MDVTimer_execute(pos) {
    	if (mdvTimer.jobs[pos] != null) {
    		var target	= mdvTimer.jobs[pos].target;
    		var func	= mdvTimer.jobs[pos].func;
    		if (mdvTimer.jobs[pos].arg != null)
    			func.apply(target, mdvTimer.jobs[pos].arg);
    		else
    			func.apply(target);

    		mdvTimer.jobs[pos] = null;
    	}
    }

   function MDVMap_onclick() {
	    this.mdvMap.events.triggerEvent(MDVEvent_OBJECT_CLICKED, this.marker.objectId);
   		return false;
   }

    
    /**
     * MDVRenderer
      * @param {object} mdvMap 
      * @param {object} container 
     */
     function MDVRenderer(mdvMap, container) {
		this.mdvMap 		= mdvMap;
     	this.container 		= container; 
     	this.max			= null;
     	this.min			= null;
     	this.border			= 10;
     	this.offset			= new MDVPoint(0, 0);
     	this.boundingBox	= null;
     }
     
     MDVRenderer.prototype.drawPolyline = function (polyline) {
     	this.mdvMap.events.triggerEvent(MDVEvent_ERROR, 'MDVMap: Not implemented.')
     	return false;
     };
     
     MDVRenderer.prototype.removePolyline = function (polyline) {
     	this.mdvMap.events.triggerEvent(MDVEvent_ERROR, 'MDVMap: Not implemented.')
     	return false;
     };
     
     MDVRenderer.prototype.setBoundingBox = function () {
     	this.mdvMap.events.triggerEvent(MDVEvent_ERROR, 'MDVMap: Not implemented.')
     	return false;
     };
     
     MDVRenderer.prototype.update = function() {
     	var coords = this.mdvMap.getPolylinePoints();
     	if (coords.length != 2 || coords[0].length <= 0 || coords[1].length <= 0)
     		return false;

     	var x = coords[0];
     	var y = coords[1];
     	
     	x.sort(MDVMap_NumSort);
     	y.sort(MDVMap_NumSort);
     	
     	this.max = new MDVPoint(x[x.length-1], y[y.length-1])
     	this.min = new MDVPoint(x[0], y[0]);

     	var bBoxMax = this.max.clone();
     	var bBoxMin = this.min.clone();
     	
	     	bBoxMax.x += this.border;
	     	bBoxMax.y += this.border;
	     	
	     	bBoxMin.x -= this.border;
	     	bBoxMin.y -= this.border;
	     	
	    this.boundingBox = [bBoxMin.clone(), bBoxMax.clone()];
	    
	    this.setBoundingBox();
	    
     	return true;
     };
     
     /**
      * MDVVMLRenderer
      * @param {object} mdvMap 
      * @param {object} container
      */
      function MDVVMLRenderer(mdvMap, container) {
		this.mdvMap 	= mdvMap;
     	this.container 		= container; 
     	this.max			= null;
     	this.min			= null;
     	this.border			= 10;
     	this.offset			= new MDVPoint(0, 0);
     	this.boundingBox	= null;
     	this.group			= null;
     	
     	document.namespaces.add("v", "urn:schemas-microsoft-com:vml");     	
     	var style = document.createStyleSheet();
		
		// shbgr removed (IE8)
     	// style.addRule('v\\:*', "behavior: url(#default#VML);");
     	
     	this.group = document.createElement('v:group');
     	this.container.appendChild(this.group);
     	
	  	// Inherit from MDVRenderer
	    for (var method in MDVRenderer.prototype) {
	
	    if (!MDVVMLRenderer.prototype[method])
	      MDVVMLRenderer.prototype[method] = MDVRenderer.prototype[method];
	    }
      }
      
      MDVVMLRenderer.prototype.drawPolyline = function (polyline) {
      	if (!polyline)
      		return false;
      		
  		var pathStr = 'm';
  		for (var i=0; i < polyline.coords.length; i++) {      			
    		var point = this.mdvMap.getPoint(polyline.coords[i]);					
    		
    		var x = point.x - this.offset.x;
    		var y = point.y - this.offset.y;
    		
    		pathStr += x + ',' + y;

    		if (i < polyline.coords.length-1)
    			pathStr += ' ';
			if (i == 0)
    			pathStr += 'l';
      	}
      	pathStr += ' e'; 
      	
		var width  = this.boundingBox[1].x - this.boundingBox[0].x;
		var height = this.boundingBox[1].y - this.boundingBox[0].y;
		var left = this.boundingBox[0].x;
		var top  = this.boundingBox[0].y;

	    var colour = polyline.get('colour');
	    if (!colour)
	    	colour = 'red';
	
	    var weight = polyline.get('weight');
	    if (!weight)
	    	weight = '4px';
	    	
	    if (!polyline.append) {
	    	
	      	var shape = document.createElement("v:shape");
	      		//shape.style.behavior = 'url(#default#VML)';
	      		shape.style.position = 'relative';
	      		shape.style.top = '0px';
	      		shape.style.left = '0px';
	      		shape.style.width = width;
	      		shape.style.height = height;
	      		
	      		shape.setAttribute('filled', 'false');
	       		shape.setAttribute('stroked', 'true');
	       		shape.setAttribute('strokecolor', colour);
	       		shape.setAttribute('strokeweight', weight);
	      		shape.setAttribute('path', pathStr);
	      		
	      	var stroke = document.createElement("v:stroke");
	      		//stroke.style.behavior = 'url(#default#VML)';
	      		stroke.setAttribute('opacity', '0.5');
	      		stroke.setAttribute('joinstyle', 'round');
	      		stroke.setAttribute('endcap', 'round');
	      		
	      		shape.appendChild(stroke);	      		
	      	this.group.appendChild(shape);
	      	polyline.element = shape;
	      	polyline.append = true;
	      	
	    } else {
	    	polyline.element.setAttribute('path', '');	    	
	    	polyline.element.setAttribute('path', pathStr);
	    }
	    
      	return true;
      };
      
      MDVVMLRenderer.prototype.setBoundingBox = function () {
			var width  = this.boundingBox[1].x - this.boundingBox[0].x;
			var height = this.boundingBox[1].y - this.boundingBox[0].y;
			
			var left = this.boundingBox[0].x;
			var top  = this.boundingBox[0].y;
		
			this.offset = new MDVPoint(left, top);
			
			this.group.style.behavior = 'url(#default#VML)';
			this.group.style.width  = width + 'px';
			this.group.style.height = height + 'px';
			this.group.style.position = 'absolute'
			this.group.style.left = left + 'px';
			this.group.style.top = top + 'px';
			this.group.setAttribute('coordsize', width + ',' + height);
		
			return true;			
      };
      
     
     /**
      * MDVSVGRenderer
      * @param {object} mdvMap 
      * @param {object} container 
      */
      function MDVSVGRenderer(mdvMap, container) {
		this.mdvMap 	= mdvMap;
     	this.container 		= container; 
     	this.max			= null;
     	this.min			= null;
     	this.border			= 10;
     	this.offset			= new MDVPoint(0, 0);
     	this.boundingBox	= null;
		
		this.svgNS		= 'http://www.w3.org/2000/svg';
		this.svg		= document.createElementNS(this.svgNS, "svg");		
		this.svg.setAttributeNS(null, 'overflow', 'visible');
		this.container.appendChild(this.svg);
		
	  	// Inherit from MDVRenderer
	    for (var method in MDVRenderer.prototype) {
	
	    if (!MDVSVGRenderer.prototype[method])
	      MDVSVGRenderer.prototype[method] = MDVRenderer.prototype[method];
	    }
      }
      
      MDVSVGRenderer.prototype.drawPolyline = function (polyline) {
      	if (!polyline)
      		return false;
      		
  		var pathStr = 'M';
  		for (var i=0; i < polyline.coords.length; i++) {      			
    		var point = this.mdvMap.getPoint(polyline.coords[i]);					
    		
    		var x = point.x - this.offset.x;
    		var y = point.y - this.offset.y;
    		
    		pathStr += x + ',' + y;

    		if (i < polyline.coords.length-1)
    			pathStr += ' ';
			if (i == 0)
    			pathStr += 'L';
      	}
     	
      	if (!polyline.append) {
		    var colour = polyline.get('colour');
		    if (!colour)
		    	colour = 'red';

		    var weight = polyline.get('weight');
		    if (!weight)
		    	weight = '4px';
	      	
      		var path = document.createElementNS(this.svgNS, 'path');
      			path.setAttributeNS(null, 'd', pathStr);
      			path.setAttributeNS(null, 'stroke-linejoin', 'round');
      			path.setAttributeNS(null, 'stroke-linecap', 'round');
      			path.setAttributeNS(null, 'stroke-opacity', '0.50');
      			path.setAttributeNS(null, 'stroke-width', weight);
      			path.setAttributeNS(null, 'stroke', colour);
      			path.setAttributeNS(null, 'fill', 'none');
			this.svg.appendChild(path);
			polyline.element = path;
	      	polyline.append = true;     
      	} else {
      		if (polyline.element)      		
      			polyline.element.setAttributeNS(null, 'd', pathStr);
      	}
      		
      	return true;
      };
      
      MDVSVGRenderer.prototype.setBoundingBox = function () {
			var width  = this.boundingBox[1].x - this.boundingBox[0].x;
			var height = this.boundingBox[1].y - this.boundingBox[0].y;
			
			var left = this.boundingBox[0].x;
			var top  = this.boundingBox[0].y;
		
			this.offset = new MDVPoint(left, top);
			
			this.svg.setAttributeNS(null, 'width', width + 'px');
			this.svg.setAttributeNS(null, 'height', height + 'px');
			this.svg.setAttributeNS(null, 'style', 'position: absolute; left: ' + left + 'px; top: ' + top + 'px;');
			
			return true;
      };
     
     /**
      * MDVPolyline
      * @param {object} mdvMap 
      * @param {object} coords 
      * @param {string} colour 
      */
      function MDVPolyline(mdvMap, coords) {
      		if (!mdvMap)
      			return;
    
      		this.mdvMap		= mdvMap;
    		this.id			= mdvMap.lastPolylineID++;
      		this.coords		= null;
      		this.toolTip	= null;
      		this.attributes = new Array();
      		this.element	= null;
      		this.renderer	= this.mdvMap.renderer;
      		this.append		= false;
      		this.markers	= new Array();
      		
      		if (typeof coords == 'string')
      			this.coords = this.parseString(coords);
      		else if (typeof coords == 'object')
      			this.coords = coords;
      }
      
      MDVPolyline.prototype.destroy = function() {
      		// External
      		this.mdvMap = null;
      		this.coords = null;
      		
      		if (this.toolTip)
      			this.toolTip.destroy();
      			
      		this.coords = null;
      		this.mdvMap = null;
      		      		
      		// DOM
      		if (this.element) {
      		
	      		if (this.element.mdvMap)
	      			this.element.mdvMap = null;

	      		this.element.onmouseover = null;
	      		this.element.onmouseout = null;
	      		this.element.onclick = null;
	      		this.element = null;
      		}
      };
      
      MDVPolyline.prototype.parseString = function (coordStr) {
      		var coordArray = coordStr.split(' ');
      		var retArr     = new Array();
      		for (var i = 0; i < coordArray.length; i++) {
      			var couple = coordArray[i].split(',');
      			if (couple[0] && couple[1]) {
	      			var tempCoords = new MDVCoordinates(this.mdvMap.config.get('mapName'), couple[0], couple[1]);
	      			retArr.push(tempCoords);
      			}
      		}
      		
      		return retArr;
      };
      
      MDVPolyline.prototype.add = function (key, value) {
      		for (var i=0; i < this.attributes.length; i++) {
      			if (this.attributes[i].key == key)
      				return false;
      		}
      		
      		this.attributes[key] = value;
      		
      		return true;
      };
      
      MDVPolyline.prototype.get = function (key) {
      		if (this.attributes[key])
      			return this.attributes[key];
      		else
      			return false;
      };
      
      MDVPolyline.prototype.getCoords = function () {
      		return this.coords;
      }; 
      
      MDVPolyline.prototype.setToolTip = function(tooltip) {
      	// TODO
	  	if (!tooltip)
	  		return false;
	  	
	  	var update = true;
	
	  	if (arguments.length == 2)
	  		update = arguments[1];
	  		
	  	var ttCoords = this.coords[0].clone();
	  		ttCoords.x = Math.floor(0.5 + (parseInt(ttCoords.x) + parseInt(this.coords[this.coords.length-1].x)) / 2);
	  		ttCoords.y = Math.floor(0.5 + (parseInt(ttCoords.y) + parseInt(this.coords[this.coords.length-1].y)) / 2);
	
	  	this.toolTip = tooltip;
	  	this.toolTip.setParent(this);
	  	this.toolTip.setCoords(ttCoords);
		if (update)
		  	this.toolTip.update();
	
		this.element.toolTip = tooltip;
	  	this.element.onmouseover = MDVMap_displayToolTip;
	  	this.element.onmouseout	 = MDVMap_hideToolTip;
		// TODO: Do we need to track down clicks as well as hovering?
	  	//this.element.onclick 	 = MDVMap_onclick;
	
	  	return true;
      };
      
      function MDVMap_NumSort (a, b) {
		  return a - b;
	  }
