// DOM2 Events  v2.0.1
// documentation: http://www.dithered.com/javascript/dom2_events/index.html
// license: http://creativecommons.org/licenses/by/1.0/
// code by Chris Nott (chris[at]dithered[dot]com)
// addWindowEventListener() and removeWindowEventListener() by Andrew Tetlaw


function DOM2Event(DOMEvent, windowEvent, functionArray) {

   // store browser's event object as a property of synthetic event object
   if (windowEvent != null) {
      this._event = windowEvent;
   }
   else if (DOMEvent != null) {
      this._event = DOMEvent;
   }

   if (functionArray && functionArray != window && functionArray.element) var element = functionArray.element;

   // add properties
   // Event interface properties
   this.type = this._event.type;
   this.currentTarget = (this._event.currentTarget != null) ? this._event.currentTarget : element;
   this.target = (this._event.target != null) ? this._event.target : (this._event.srcElement != null) ? this._event.srcElement : (element == window && this._event.type == 'load') ? document : null;
   this.eventPhase = (this._event.eventPhase != null) ? this._event.eventPhase : (this.target == this.currentTarget) ? this.AT_TARGET : this.BUBBLING_PHASE;
   this.timeStamp = new Date();  // discard buggy Mozilla timeStamp
   this.bubbles = (this._event.bubbles !== null) ? this._event.bubbles : (this.type == 'blur' || this.type == 'focus' || this.type == 'load' || this.type == 'unload') ? false : true;
   this.cancelable = (this._event.cancelable !== null) ? this._event.cancelable : (this.type == 'click' || this.type == 'mousedown' || this.type == 'mouseout' || this.type == 'mouseover' || this.type == 'mouseup' || this.type == 'submit') ? true : false;

   // UIEvent interface properties
   this.view = (this._event.view != null) ? this._event.view : window;
   this.detail = (this._event.detail !== null) ? this._event.detail : (this.type == 'click') ? 1 : (this.type = 'dblclick') ? 2 : 0;

   // MouseEvent interface properties
   this.button = DOM2Event._getButton(this._event.button);
   this.altKey = (this._event.altKey !== null) ? this._event.altKey : false;
   this.ctrlKey = (this._event.ctrlKey !== null) ? this._event.ctrlKey : false;
   this.metaKey = (this._event.metaKey !== null) ? this._event.metaKey : false;
   this.shiftKey = (this._event.shiftKey !== null) ? this._event.shiftKey : false;
   this.clientX = DOM2Event._getClientX(this._event.clientX);
   this.clientY = DOM2Event._getClientY(this._event.clientY);
   this.layerX = DOM2Event._getLayerX(this._event.layerX, this._event.offsetX);
   this.layerY = DOM2Event._getLayerY(this._event.layerY, this._event.offsetY);
   this.screenX = (this._event.screenX !== null) ? this._event.screenX : 0;
   this.screenY = (this._event.screenY !== null) ? this._event.screenY : 0;
   this.relatedTarget = (this._event.relatedTarget != null) ? this._event.relatedTarget : (this.type == 'mouseover' && this._event.fromElement != null) ? this._event.fromElement : (this.type == 'mouseout' && this._event.toElement != null) ? this._event.toElement : null;

   // useful extensions
   this.pageX = DOM2Event._getPageX(this._event.pageX, this._event.clientX);
   this.pageY = DOM2Event._getPageY(this._event.pageY, this._event.clientY);
   this.keyCode = (this._event.keyCode) ? this._event.keyCode : null;
   this.charCode = (this._event.charCode) ? this._event.charCode : null;
}

DOM2Event.prototype.stopPropagation = function() {
   if (this._event.stopPropagation != null) {
      this._event.stopPropagation();
   }
   else if (this._event.cancelBubble !== null) {
      this._event.cancelBubble = true;
   }
};

DOM2Event.prototype.preventDefault = function() {
   if (this._event.preventDefault != null) {
      this._event.preventDefault();
   }
   else if (this._event.returnValue !== null) {
      this._event.returnValue = false;
   }
};

// event phase constants
if (Event == null) {
   var Event = {
      CAPTURING_PHASE : 1,
      AT_TARGET : 2,
      BUBBLING_PHASE : 3
   };
}


/*****************************************************************************
   Enable DOM2 event listener registration
 *****************************************************************************/

// add attachEventListener() and removeEventListener() to elements
DOM2Event.initRegistration = function(element) {
   if (element != null) {
      if (!element.addEventListener) {
         element.addEventListener = (element == window) ? DOM2Event.addWindowEventListener : DOM2Event.addEventListener;
         element.removeEventListener = (element == window) ? DOM2Event.removeWindowEventListener : DOM2Event.removeEventListener;
      }
   }
   else if (!document.addEventListener && (document.all || document.getElementsByTagName)) {
      window.addEventListener = DOM2Event.addWindowEventListener;
      window.removeEventListener = DOM2Event.removeWindowEventListener;

      var allTags = new Array();

      // for IE4+, Op6
      if (document.all) {
         allTags = document.all;
      }

      // for Op5
      else if (document.getElementsByTagName) {
         allTags = document.getElementsByTagName('*');
      }

      document.addEventListener = DOM2Event.addEventListener;
      document.removeEventListener = DOM2Event.removeEventListener;
      for (element in allTags) {
         if (typeof element != 'object' && typeof allTags[element] != 'undefined') {
            // allTags[element] is undefined from time to time in IE/mac, I was unable to
            // figure out when exactly however.
            if (typeof allTags[element].length == 'number' && ! allTags[element].nodeType) {
               // this is for when we're using document.all and allTags[element] is an object collection
               // this occurs, as an example, when one has multiple checkboxes w/ the same name
               // the main problem with this is IE (mac & win) gathers the objects up into the collection
               // but still has N entries in document.all, so this adds the eventListener N times
               // per object
               for (var i = 0; i < allTags[element].length; ++i) {
                  if (typeof allTags[element][i] != 'undefined') {
                     allTags[element][i].addEventListener = DOM2Event.addEventListener;
                     allTags[element][i].removeEventListener = DOM2Event.removeEventListener;
                  }
               }
            } else {
               allTags[element].addEventListener = DOM2Event.addEventListener;
               allTags[element].removeEventListener = DOM2Event.removeEventListener;
            }
         }
      }
   }
};


/*****************************************************************************
   Event listener registration functions
 *****************************************************************************/

// mimic event listeners by maintaining a list of event listener functions that are called thru a DOM0 event handler
// add an event listener to the list
DOM2Event.addEventListener = function(eventType, eventListener) {
   if (!this['on' + eventType]) {
      this['on' + eventType] = function(e) {
         if (!e) e = event;
         var functionArray = eval('this.' + e.type + 'Handler');
         for (var index = 0; index < functionArray.length; index++) {
            if (functionArray[index] != null) {
               functionArray[index](e);
            }
         }
      };
      this[eventType + 'Handler'] = new Array();
      this[eventType + 'Handler'].element = this;
   }
   var index = 0;
   while (this[eventType + 'Handler'][index] != null) {
      index++;
   }
   this[eventType + 'Handler'][index] = eventListener;
};

// remove an event listener from the list
DOM2Event.removeEventListener = function(eventType, eventListener) {
   var functionArray = this[eventType + 'Handler'];
   if(functionArray)
   {
   for (var index = 0; functionArray && index < functionArray.length; index++) {
      if (functionArray[index] == eventListener) {
         functionArray[index] = null;
      }
   }
   }
};

// in IE, window and this aren't completely identical
// to store the event function, need to explicitly use window instead of this
DOM2Event.addWindowEventListener = function(eventType, eventListener) {
   if (!window['on' + eventType]) {
      window['on' + eventType] = function(e) {
         if (!e) e = event;
         var functionArray = eval('window.' + e.type + 'Handler');
         for (var index = 0; index < functionArray.length; index++) {
            if (functionArray[index] != null) {
               functionArray[index](e);
            }
         }
      };
      window[eventType + 'Handler'] = new Array();
      window[eventType + 'Handler'].element = window;
   }
   var index = 0;
   while (window[eventType + 'Handler'][index] != null) {
      index++;
   }
   window[eventType + 'Handler'][index] = eventListener;
};

// remove an event listener from the list
DOM2Event.removeWindowEventListener = function(eventType, eventListener) {
   var functionArray = window[eventType + 'Handler'];
   if(functionArray){
   for (var index = 0; index < functionArray.length; index++) {
      if (functionArray[index] == eventListener) {
         functionArray[index] = null;
      }
   }
   }
};

/*****************************************************************************
   Methods to fix DOM2 implementations
 *****************************************************************************/

// returns correct button number: left - 0, center - 1, right - 2
DOM2Event._getButton = function(currentButton) {
   if (currentButton != null) {
      var ua = navigator.userAgent.toLowerCase();
      var isGecko = (ua.indexOf('gecko') != -1);
      var isNS60x = (isGecko && ua.indexOf('netscape') != -1 && parseFloat(navigator.appVersion) == 6);

      // in NS6.0x and Opera, button numbers are: left - 1, center - 2, right - 3
      if (isNS60x || ua.indexOf('opera') != -1) {
         return currentButton - 1;
      }

      // in IE4+, Koqueror and Safari, button numbers are: left - 1, center - 4, right - 2 but can be combined
      // return button with highest number
      else if (ua.indexOf('msie') != -1 || ua.indexOf('konqueror') != -1 || ua.indexOf('safari') != -1) {
         return (currentButton >= 4) ? 1 : ( (currentButton >= 2) ? 2 : 0);
      }

      // in NS6.1+ and Mozilla, button numbers are ok
      else if (isGecko) {
         return currentButton;
      }
   }
   return null;
};

// returns the distance from window left to event left
DOM2Event._getClientX = function(currentClientX) {
   var ua = navigator.userAgent.toLowerCase();

   // in Konqueror, Opera and iCab, clientX really contains the pageX value
   if (ua.indexOf('konqueror') != -1 || ua.indexOf('safari') != -1 || ua.indexOf('opera') != -1 || ua.indexOf('icab') != -1) {
      if (document.body && document.body.scrollLeft != null) {
         return currentClientX - document.body.scrollLeft;
      }
      return currentClientX;
   }

   // in IE and NS, a good clientX exists
   else if (currentClientX) {
      return currentClientX;
   }

   else {
      return null;
   }
};

// returns the distance from window top to event top
DOM2Event._getClientY = function(currentClientY) {
   var ua = navigator.userAgent.toLowerCase();

   // in Konqueror, Opera and iCab, clientY really contains the pageY value
   if (ua.indexOf('konqueror') != -1 || ua.indexOf('safari') != -1 || ua.indexOf('opera') != -1 || ua.indexOf('icab') != -1) {
      if (document.body && document.body.scrollTop != null) {
         return currentClientY - document.body.scrollTop;
      }
      return currentClientY;
   }

   // in IE and NS, a good clientY exists
   else if (currentClientY) {
      return currentClientY;
   }

   else {
      return null;
   }
};

//returns the distance from the element left to event left
DOM2Event._getLayerX = function(currentLayerX, currentOffsetX) {
   // mozilla, safari
   if (currentLayerX != null) {
      return currentLayerX;
   }
   else if (currentOffsetX != null) {
      return currentOffsetX;
   }
   return null
};

//returns the distance from the element top to event top
DOM2Event._getLayerY = function(currentLayerY, currentOffsetY) {
   // mozilla, safari
   if (currentLayerY != null) {
      return currentLayerY;
   }
   else if (currentOffsetY != null) {
      return currentOffsetY;
   }
   return null
};


// returns the distance from document left to event left
DOM2Event._getPageX = function(currentPageX, currentClientX) {
   var ua = navigator.userAgent.toLowerCase();

   // in cases where a pageX exists, it's good
   if (currentPageX) {
      return currentPageX;
   }

   // in IE, add scrollLeft to clientX
   else if (ua.indexOf("msie") != -1 && ua.indexOf("opera") == -1) {
      if (document.documentElement && document.documentElement.scrollLeft > 0) {
         return (currentClientX + document.documentElement.scrollLeft);
      }
      else if (document.body != null && document.body.scrollLeft > 0) {
         return (currentClientX + document.body.scrollLeft);
      }
      else {
         return currentClientX;
      }
   }

   // in Konqueror, Opera and iCab, clientX really contains the pageX value
   else if (currentClientX) {
      return currentClientX;
   }

   else {
      return null;
   }
};

// returns the distance from document top to event top
DOM2Event._getPageY = function(currentPageY, currentClientY) {
   var ua = navigator.userAgent.toLowerCase();

   // in cases where a pageY exists, it's good
   if (currentPageY) {
      return currentPageY;
   }

   // in IE, add scrollTop to clientY
   else if (ua.indexOf("msie") != -1 && ua.indexOf("opera") == -1) {
      if (document.documentElement && document.documentElement.scrollTop > 0) {
         return (currentClientY + document.documentElement.scrollTop);
      }
      else if (document.body != null && document.body.scrollTop > 0) {
         return (currentClientY + document.body.scrollTop);
      }
      else {
         return currentClientY;
      }
   }

   // in Konqueror, Opera and iCab, clientY really contains the pageY value
   else if (currentClientY) {
      return currentClientY;
   }

   else {
      return null;
   }
};
// Copyright 2005 Aptas, Inc.; all rights reserved.

var EQUITORIAL_EARTH_RADIUS_MEAN_METERS = 6.378245e6;
var HALF_PI = Math.PI / 2.0;

// Simple coordinate class
function coord(x, y) {
    this.x = x;
    this.y = y;
}

function radians(deg) { return deg * (Math.PI / 180.0); }
function degrees(rad) { return rad * (180.0 / Math.PI) }

// GridSystem class
function GridSystem(p, x_size, y_size, x_units, y_units, invert_y, origin_lon, origin_lat) {
    this._P = p;
    this._x_grid_size = x_size;
    this._y_grid_size = y_size;
    this._x_upg = x_units;
    this._y_upg = y_units;

    this._invert_y = invert_y;
    this._origin_lon = radians(origin_lon);
    this._origin_lat = radians(origin_lat);

    this.reset();
}

// "Static" method to create a new GridSystem object for a given grid size (meters)
GridSystem.newInstance = function( size_x, size_y, tile_width, tile_height ) {
    return new GridSystem( new EquiRectangularMapProjection( radians(0.0), radians(40) ),
                           size_x,
                           size_y,
                           tile_width,
                           tile_height,
                           true,
                           -180.0,
                           90.0 );
};

GridSystem.prototype.reset = function() {
    var pt = this._P.forward( this._origin_lon, this._origin_lat );
    this._x_translation = - pt.x;
    this._y_translation = - pt.y;
    this._x_scale = (this._x_upg / this._x_grid_size);
    this._y_scale = (this._y_upg / this._y_grid_size) * (this._invert_y ? -1.0 : 1.0);
};

GridSystem.prototype.getXScale = function() {
    return this._x_scale;
}

GridSystem.prototype.getYScale = function() {
    return this._y_scale;
}

/**
* Calculate the x grid number of coordinate.
*
* @param x X coordinate in grid units.
*/
GridSystem.prototype.gridX = function(x) {
    return Math.floor(x / this._x_upg);
};

/**
 * Calculate the y grid number of coordinate.
 *
 * @param y Y coordinate in grid units.
 */
GridSystem.prototype.gridY = function(y) {
    return Math.floor(y / this._y_upg);
};

/**
* Calculate the x/y grid number of coordinate.
*
* @param x X coordinate in map units.
* @param y Y coordinate in map units.
* - or -
* @param pt Coordinate in map units.
*/
GridSystem.prototype.grid = function( x, y ) {
    if( arguments.length == 1 ) {
        var pt = x;
        return new coord( this.gridX( pt.x ), this.gridY( pt.y ) );
    } else {
        return new coord( this.gridX(x), this.gridY(y) );
    }
};

/**
 * Calculate the x offset position within the x grid.
 *
 * @param x X coordinate in grid units.
 */
GridSystem.prototype.offsetX = function(x) {
    return x % this._x_upg;
};

/**
 * Calculate the y offset position within the y grid.
 *
 * @param y Y coordinate in grid units.
 */
GridSystem.prototype.offsetY = function(y) {
    return y % this._y_upg;
};

/**
* Calculate the x/y offset position within the x/y grid.
*
* @param x X coordinate in map units.
* @param y Y coordinate in map units.
*/
GridSystem.prototype.offset = function(x, y) {
    if( arguments.length == 1 ) {
        var pt = x;
        return new coord( this.offsetX( pt.x ), this.offsetY( pt.y ) );
    } else {
        return new coord( this.offsetX( x ), this.offsetY( y ) );
    }
};


// get x grid coordinate of given grid and offset
GridSystem.prototype.toX = function(grid) {
    var offset = ( arguments.length == 2 ? arguments[1] : 0 );
    return (grid * this._x_upg ) + offset;
};

// get y grid coordinate of given grid and offset
GridSystem.prototype.toY = function(grid) {
    var offset = ( arguments.length == 2 ? arguments[1] : 0 );
    return (grid * this._y_upg ) + offset;
};

// get x,y grid coordinate of given grids/offsets
GridSystem.prototype.toXY = function(gridX, offsetX, gridY, offsetY) {
    if( arguments.length == 2 ) {
        var lon = gridX;
        var lat = offsetX;
        var xy = this._P.forward( radians(lon), radians(lat) );
        return new coord( (xy.x + this._x_translation) * this._x_scale,
                          (xy.y + this._y_translation) * this._y_scale );
    } else {
        return new coord( this.toX(gridX, offsetX),
                          this.toY(gridY, offsetY));
    }
};

/**
 * Convert an X/Y grid coordinate to a longitude/latitude.
 *
 * @param x
 * @param y
 * @return the corresponding longitude/latitude coordinate.
 */
GridSystem.prototype.toLL = function(x, y) {
    if( arguments.length == 1 ) {
        y = arguments[0].y;
        x = arguments[0].x;
    }
    var ll =  this._P.inverse( (x / this._x_scale) - this._x_translation,
                               (y / this._y_scale) - this._y_translation );
    ll.x = degrees(ll.x);
    ll.y = degrees(ll.y);
    return ll;
};

/**
 * Calculate the grid coordinate for the upper-left point of an image
 * given the center lon/lat and image dimensions.
 *
 * @param center_lon
 * @param center_lat
 * @param width
 * @param height
 * @return
 */
GridSystem.prototype.getUL = function(center_lon, center_lat, width, height) {
    var xy = this.toXY(center_lon, center_lat);
    if (this._invert_y)
        return new coord( xy.x - (width / 2.0),
                          xy.y - (height / 2.0) );
    else
        return new coord( xy.x - (width / 2.0),
                          xy.y + (height / 2.0) );
};

/**
 * Calculate the grid coordinate for the lower-right point of an image
 * given the center lon/lat and image dimensions.
 *
 * @param center_lon
 * @param center_lat
 * @param width
 * @param height
 * @return
 */
GridSystem.prototype.getLR = function(center_lon, center_lat, width, height) {
    var xy = this.toXY(center_lon, center_lat);
    if (this._invert_y)
        return new coord( xy.x + (width / 2.0),
                          xy.y + (height / 2.0) );
    else
        return new coord( xy.x + (width / 2.0),
                          xy.y - (height / 2.0) );
};

/**
 * Calculate the grid coordinate for the lower-left point of an image
 * given the center lon/lat and image dimensions.
 *
 * @param center_lon
 * @param center_lat
 * @param width
 * @param height
 * @return
 */
GridSystem.prototype.getLL = function(center_lon, center_lat, width, height) {
    xy = this.toXY(center_lon, center_lat);
    if (this._invert_y)
        return new coord( xy.x - (width / 2.0),
                          xy.y + (height / 2.0) );
    else
        return new coord( xy.x - (width / 2.0),
                          xy.y - (height / 2.0) );
};

/**
 * Calculate the grid coordinate for the upper-right point of an image
 * given the center lon/lat and image dimensions.
 *
 * @param center_lon
 * @param center_lat
 * @param width
 * @param height
 * @return
 */
GridSystem.prototype.getUR = function(center_lon, center_lat, width, height) {
    xy = this.toXY(center_lon, center_lat);
    if (this._invert_y)
        return new coord( xy.x + (width / 2.0),
                          xy.y - (height / 2.0) );
    else
        return new coord( xy.x + (width / 2.0),
                          xy.y + (height / 2.0) );
};


// Equi-Rectangular Map Projection class
function EquiRectangularMapProjection(lon, lat) {
    this._center_lon = lon;          // center longitude (projection center)
    this._center_lat = lat;          // center latitude
    this._cos_center_lat = Math.cos(lat); // cosine of the center latitude
    this._R = EQUITORIAL_EARTH_RADIUS_MEAN_METERS;
}

function adjust_lon(lon) {
   if (lon < -Math.PI)
      return lon - (Math.floor(lon / Math.PI) * Math.PI);
   else if (lon > Math.PI)
      return -(Math.PI - lon + (Math.floor(lon / Math.PI) * Math.PI));
   return lon;
}

EquiRectangularMapProjection.prototype.forward = function(lon, lat) {
    return new coord( this._R * adjust_lon(lon - this._center_lon) * this._cos_center_lat,
                      this._R * lat );
};

EquiRectangularMapProjection.prototype.inverse = function(x, y) {
    return new coord( adjust_lon(this._center_lon + x / (this._R * this._cos_center_lat)),
                      Math.min(y / this._R, HALF_PI) );
};
/*
 * Configuration Parameters
 */

var DSMapConfig = {
    'background': '#f2f0e1',
    'singleZoom': 3,
    'emptyZoom': 13,
    'defaultLat': 39.73926,
    'defaultLng': -104.98478,

    'imageBase': 'http://images.myareaguide.com/maps/img/',
    'pixelUrl': 'http://images.myareaguide.com/blank.gif',
    'brokenUrl': 'http://images.myareaguide.com/maps/img/map_unavailable.gif',

    'tileBase': 'http://localhost/tiles/',
    'tileExtension': '.png',
    'tileSuffix': '',
    'tileWidth': 256,
    'tileHeight': 256,
    'tileAttempts': 1,

    'slidePixels': 30,
    'slideDelay': 30
};

/**
 * creates a DSPoint
 *
 * @param lat Lattitude of the point
 * @param lng Longitude of the point
 * @return new DSPoint object
 * @constructor
 */
function DSPoint( lat, lng ) {
    this.lat = lat;
    this.lng = lng;
}

/* DSSlider */
/**
 * Creates a vertical slider control for the zoom bar
 *
 * @param map The map this slider is for
 * @param slider The image element for the slider
 * @param thumb The thumb image element
 * @constructor
 */
function DSSlider( map, slider, thumb ) {
    this.map = map;
    this.slider = slider;
    this.thumb = thumb;
    var o = this;
    this.thumb.addEventListener( 'mousedown', function( e ) { o.mouseDown( e ) }, false );
    this.slider.addEventListener( 'mousedown', function( e ) { o.mouseDown( e ) }, false );
    this.drag = false;
    this.usableHeight = this.slider.height - this.thumb.height;
    this.top = parseInt( this.slider.style.top );
    this.bottom = this.top + this.usableHeight;
    this.offsetTop = ( DSMap.GetOffsets( this.slider.parentNode ) ).y;
}
DSSlider.prototype.mouseDown = function( evt ) {
    var e = new DOM2Event( evt, window.event, this );
    if( e.button == 0 ) {
        if( e.target == this.slider ) {
            this.thumb.style.top = ( e.pageY - this.offsetTop - ( this.thumb.height / 2 ) ) + 'px';
        }
        this.mapCenter = this.map.getCenterPoint();
        this.zoomLeft = parseInt( this.map.mapLayer.style.left );
        this.zoomLeftAdjusted = this.map.getMapLeft();
        this.zoomTop = parseInt( this.map.mapLayer.style.top );
        this.zoomTopAdjusted = this.map.getMapTop();
        this.map.hideObjects();
        this.prevY = e.pageY - this.thumb.offsetTop - this.offsetTop;
        this.drag = true;
        this.map.mouseMove( evt );
        e.preventDefault();
        e.stopPropagation();
    }
}
DSSlider.prototype.getPosition = function() {
    var p = ( parseInt( this.thumb.style.top ) - this.top ) / this.usableHeight;
    return p;
}
DSSlider.prototype.setPosition = function( p ) {
    this.thumb.style.top = this.top + Math.round( p * this.usableHeight ) + 'px';
    return p;
}
DSSlider.prototype.mouseUp = function() {
    this.drag = false;
    this.prevY = null;
    this.map.showObjects();
    return this.getPosition();
}
DSSlider.prototype.mouseMove = function( e ) {
    var y = e.pageY - this.offsetTop - this.prevY;
    if( y < this.top ) {
        y = this.top;
    }
    if( y > this.bottom ) {
        y = this.bottom;
    }
    this.thumb.style.top = y + 'px';
    return this.getPosition();
}
/*
 * DSTile
 */
function DSTile( parent, x, y, z, map ) {
    this.img = DSMap.createElement(  parent, 'img', '', 'src', DSMapConfig.pixelUrl, 'style', 'position: absolute;', 'galleryImg', 'no' );
    this.img.addEventListener( 'load', DSTile.load, false );
    this.img.addEventListener( 'error', DSTile.error, false );
    this.x = x;
    this.y = y;
    this.map = map;
}
DSTile.errors = new Object();
DSTile.error = function( evt ) {
    var e = new DOM2Event( evt, window.event, this );
    if( ! DSTile.errors[e.currentTarget.src] ) {
        DSTile.errors[e.currentTarget.src] = 1;
    }
    if( DSTile.errors[e.currentTarget.src]++ < DSMapConfig.tileAttempts ) {
        var s = e.currentTarget.src;
        e.currentTarget.src = DSMapConfig.pixelUrl;
        e.currentTarget.src = s;
    } else if( ! e.currentTarget.src.match( DSMapConfig.brokenUrl ) ) {
        e.currentTarget.src = DSMapConfig.brokenUrl;
    }
}
DSTile.load = function( evt ) {
    var e = new DOM2Event( evt, window.event, this );
    e.currentTarget.style.visibility = 'visible';
}

DSTile.prototype.setZoomLevel = function( z ) {
    this.zoomLevel = z;
}
DSTile.prototype.addGridXOffset = function( x ) {
    this.gridX = this.gridX + x;
}
DSTile.prototype.addGridYOffset = function( y ) {
    this.gridY = this.gridY + y;
}
DSTile.prototype.setGridXY = function( x, y ) {
    this.gridX = x;
    this.gridY = y;
}
DSTile.prototype.setMapOffsetXY = function( x, y ) {
    this.mlOffsetLeft = x;
    this.mlOffsetTop = y;
}
DSTile.prototype.setX = function( x ) {
    this.x = x;
}
DSTile.prototype.setY = function( y ) {
    this.y = y;
}
DSTile.prototype.setSize = function( width, height ){
    this.img.width = width;
    this.img.height = height;
}
DSTile.prototype.position = function() {
    this.img.style.left = ( this.x * this.img.width + this.mlOffsetLeft ) + 'px';
    this.img.style.top = ( this.y * this.img.height + this.mlOffsetTop ) + 'px';
}
DSTile.prototype.reset = function() {
    this.img.width = DSMapConfig.tileWidth;
    this.img.height = DSMapConfig.tileHeight;
    this.position();
}
DSTile.prototype.setUrl = function() {
    this.img.style.visibility = 'hidden';
    if( typeof this.img.complete != 'undefined' && ! this.img.complete ) {
        // IE doesn't seem to trigger a load event if you change images before the original image is done loading
        var o = this;
        window.setTimeout( function() { o.setUrl(); }, 1000 );
        return;
    }
    if( this.zoomLevel && this.gridX && this.gridY ) {
    	var newzoom = this.zoomLevel;
        var s = DSMapConfig.tileBase + newzoom + '/' + this.gridX + '/' + this.gridY + DSMapConfig.tileExtension + '?w=' + this.map.containerWidth + '&h=' + this.map.containerHeight + DSMapConfig.tileSuffix;
        if( this.img.src != s ) {
            this.img.src = s;
        } else {
            this.img.style.visibility = 'visible';
        }
    } else {
        this.img.src = DSMapConfig.brokenUrl;
    }
}

/*
 * DSMap
 */
/**
 * Create a DSMap object
 *
 * @param container A pointer to the html element that will contain the map
 * @return A DSMap object
 * @constructor
 */
function DSMap( container ) {
/* TODO: would this snippet work?
 * if( ! window.addEventListener ) { DOM2Event.initRegistration() }
 */
    this.container = container;
    if( ! container ) return;
    this.drag = false;
    this.prevX = this.prevY = null;
    this.originPoint = null;
    this.zoomLevel = DSMapConfig.emptyZoom;
    this.setScale();
    this.containerWidth = parseInt( this.container.clientWidth );
    this.containerHeight = parseInt( this.container.clientHeight );
    this.columns = Math.ceil( this.containerWidth / DSMapConfig.tileWidth ) + 2;
    this.rows = Math.ceil( this.containerHeight / DSMapConfig.tileHeight ) + 2;
    this.mapLayerWidth = this.columns * DSMapConfig.tileWidth;
    this.mapLayerHeight = this.rows * DSMapConfig.tileHeight;
    this.initContainer();
    this.objects = new DSMapObject_Collection();
    this.events = new DSEvent();
    this.events.registerEvent( 'zoom' );
    this.events.registerEvent( 'recenter' );
    this.draggingEnabled = false;
}
DSMap.prototype.addEventListener = function( type, callback ) {
    this.events.addListener( type, callback );
}

/*
 * Utility functions
 */
DSMap.GetOffsets = function( el ) {
    var xy = new coord( 0, 0 );
    while( el ){
        xy.x += el.offsetLeft;
        xy.y += el.offsetTop;
        el = el.offsetParent
    }
    return xy;
}
DSMap.getImgSrc = function( img ) {
/*@cc_on
    @if (@_win32)
        if( img && img.filters && img.filters[0] ) {
            return img.filters[0].src
        } else if( img ) {
    @*/
            return img.src;
    /*@
        }
    @end
@*/
}
DSMap.setImgSrc = function( img, src, sizing ) {
/*@cc_on
    @if (@_win32)
        if( src.match( /\.png(;|$)/ ) ) {
            var s = sizing ? ', sizingMethod="' + sizing + '"' : '';
            img.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '"' + s + ')';
            img.src = 'img/pixel_trans.gif';
        } else if (img.src != src) {
    @*/
            img.src = src;
    /*@
        }
    @end
@*/
}
DSMap.setCursor = function( el, cursor ) {
    try {
        el.style.cursor = cursor;
    } catch( e ) { /* IE5 */
        if( cursor == 'pointer' ) {
            el.style.cursor = 'hand';
        }
    }
}
function DSMap_CreateImage( src, parent, left, top, zIndex, width, height, alt, title ) {
    var i = document.createElement( 'img' );
    DOM2Event.initRegistration( i );
    i.galleryImg = 'no'; // to get rid of the IE image tool bar
    i.style.position = 'absolute';
    if( typeof i.style.filter != 'undefined' ) {
        i.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '", sizingMethod=\'scale\')';
        i.src = DSMapConfig.pixelUrl;
    } else {
        i.src = src;
    }
    if( left != undefined ) {
        i.style.left = left + 'px';
    }
    if( top != undefined ) {
        i.style.top = top + 'px';
    }
    if( zIndex != undefined ) {
        i.style.zIndex = zIndex;
    }
    if( width != undefined ) {
        i.width = width;
    }
    if( height != undefined ) {
        i.height = height;
    }
    if( alt != undefined ) {
        i.alt = alt;
    }
    if( title != undefined ) {
        i.title = title;
    }
    if( parent != undefined ) {
        parent.appendChild( i );
    }
    return i;
}
DSMap.createElement = function( parent, type, val ) {
    var el;
    if ( !type || type == "text" ) {
        el = document.createTextNode( val );
    } else {
        el = document.createElement( type );
        if ( val ) { el.appendChild( document.createTextNode( val ) ); }

        for ( var i=3; i<arguments.length; i++ ) {
            if( arguments[i] == 'style' && document.all && ! window.opera ) {
                // you can't use setAttribute w/ "style" in IE
                el.style.cssText = arguments[++i];
            } else {
                el.setAttribute( arguments[i], arguments[++i] );
            }
        }
        DOM2Event.initRegistration( el );
    }
    if( parent ) {
        parent.appendChild( el );
    }
    return el;
}

/**
 * Enables user interaction with the map.  In addition to dragging the map
 * this enables double-clicking to recenter the map.
 *
 * This is also called by <code>DSMap.addControls</code>
 */
DSMap.prototype.enableDragging = function() {
    if( ! this.draggingEnabled ) {
        this.draggingEnabled = true;
        var o = this;
        document.addEventListener( 'mousemove', function( e ) { o.mouseMove( e ) }, false );
        document.addEventListener( 'mouseup', function( e ) { o.mouseUp( e ) }, false );
        if( navigator.userAgent.indexOf( "Safari" ) != -1 ) {
            this.viewport.ondblclick = function( e ) { o.doubleClick( e ) };
        } else {
            this.viewport.addEventListener( 'dblclick', function( e ) { o.doubleClick( e ) }, false );
        }
    }
}
/**
 * Adds the pan and zoom controls to the map
 *
 * Also calls <code>DSMap.enableDragging</code> to enable more user interaction
 *
 * @param type The type of controls to add, defaults to DSMap.LARGE_CONTROLS
 */
DSMap.prototype.addControls = function( type ) {
    var o = this;
    if( ! type ) {
        type = DSMap.LARGE_CONTROLS;
    }

    // zoom controls
    var i;
    if( type.zoom ) {
        var t = type.zoom.zoomIn;
        i = DSMap_CreateImage(  t[0], this.viewport, t[1], t[2], 100 + t[3], t[4], t[5] );
        DSMap.setCursor( i, 'pointer' );
        this.bindEvent( i, 'click', this, t[6] );
        i.addEventListener( 'dblclick', DSMap_CancelEvent, false ); // otherwise double-clicking on these controls recenters after panning/zooming

        t = type.zoom.zoomOut;
        i = DSMap_CreateImage(  t[0], this.viewport, t[1], t[2], 100 + t[3], t[4], t[5] );
        DSMap.setCursor( i, 'pointer' );
        this.bindEvent( i, 'click', this, t[6] );
        i.addEventListener( 'dblclick', DSMap_CancelEvent, false ); // otherwise double-clicking on these controls recenters after panning/zooming

        if( type.zoom.slider && type.zoom.thumb ) {
            t = type.zoom.slider;
            var s = DSMap_CreateImage( t[0], this.viewport, t[1], t[2], 100 + t[3], t[4], t[5] );
            t = type.zoom.thumb;
            i = DSMap_CreateImage(  t[0], this.viewport, t[1], t[2], 100 + t[3], t[4], t[5] );
            this.zoomSlider = new DSSlider( this, s, i );
        }
    }

    var p = type.pan;
    for( var j = 0; j < p.length; ++j ) {
        i = DSMap_CreateImage( p[j][0], this.viewport, p[j][1], p[j][2], 100 + p[j][3], p[j][4], p[j][5] );
        DSMap.setCursor( i, 'pointer' );
        if( p[j][6] ) {
            this.bindEvent( i, 'click', this, p[j][6] );
        }
        i.addEventListener( 'dblclick', DSMap_CancelEvent, false );
    }

    this.enableDragging();
}

/* Zoom Controls */
/**
 * Sets the zoom level, updates the zoom slider (if it exists)
 * and loads and centers the new map
 *
 * @param z zoomlevel
 */
DSMap.prototype.setZoomLevel = function( z, p ) {
    if( z < 1 ) {
        z = 1;
    } else if( z > DSMap.Scales.length ) {
        z = DSMap.Scales.length;
    }
    if( this.zoomSlider ) {
        this.zoomSlider.setPosition( ( ( z - 1 ) / ( DSMap.Scales.length - 1 ) ) );
    }
    this.originPoint = p ? p : this.getCenterPoint();
    this.zoomLevel = z;
    this.setScale();
    this.loadTiles();
    this.events.triggerEvent( 'zoom', {'zoomLevel':z}, this );
}
/**
 * Zooms in 1 level
 */
DSMap.prototype.zoomIn = function() {
    this.setZoomLevel( this.zoomLevel - 1 );
}
/**
 * Zooms out 1 level
 */
DSMap.prototype.zoomOut = function() {
    this.setZoomLevel( this.zoomLevel + 1 );
}
/**
 * Pans and zooms the map so all DSMapObjects are visible
 * If only 1 object exists on the map, the zoom level is set to DSMapConfig.singleZoom
 * If there are no objects on the map, the zoom level is set to DSMapConfig.emptyZoom
 * and the map is centered on DSMapConfig.defaultLat, DSMapConfig.defaultLng
 *
 * @param factor The amount of map that should contain all the objects
 *               This defaults to .9, so the center 90% of the map should contain everything
 */
DSMap.prototype.bestFit = function( factor ) {
    if( typeof factor != 'number' ) {
        factor = .9;
    }
    var p;
    var it = new DSMapObject_Iterator( this.objects );

    if( it.hasNext() ) {
        p = it.next();
        var P = this.gridSystem._P;
        var maxpt = P.forward( radians( p.point.lng ), radians( p.point.lat ) );
        var minpt = new coord( maxpt.x, maxpt.y );

        while( it.hasNext() ) {
            p = it.next();
            var pt = P.forward( radians( p.point.lng ), radians( p.point.lat ) );
            if( pt.x < minpt.x ) minpt.x = pt.x;
            if( pt.x > maxpt.x ) maxpt.x = pt.x;
            if( pt.y < minpt.y ) minpt.y = pt.y;
            if( pt.y > maxpt.y ) maxpt.y = pt.y;
        }

        if( minpt.x == maxpt.x && minpt.y == maxpt.y ) {
            var ll = P.inverse( minpt.x, minpt.y );
            ll.x = degrees( ll.x );
            ll.y = degrees( ll.y );
            this.centerAndZoom( new DSPoint( ll.y, ll.x ), DSMapConfig.singleZoom );
        } else {
            var midll = P.inverse( ( minpt.x + maxpt.x ) / 2.0, ( minpt.y + maxpt.y ) / 2.0 );
            midll.x = degrees( midll.x );
            midll.y = degrees( midll.y );

            var dist_x = maxpt.x - minpt.x;
            var dist_y = maxpt.y - minpt.y;

            var width  = this.containerWidth;
            var height = this.containerHeight;

            // adjust horizontal distance to figure out the corresponding width radius
            dist_x *= width / height;

            var radius = Math.max( dist_x, dist_y ) / factor;
            for( var i = 0; i < DSMap.Scales.length; ++i ) {
               if( radius <= ( width / DSMap.Grid[i].getXScale() ) ) {
                    break;
                }
            }

            var zoom = Math.min( i, DSMap.Scales.length - 1 );
            var midPoint = new DSPoint( midll.y, midll.x );
            this.centerAndZoom( midPoint, zoom + 1 );
        }
    } else {
        if( DSMapConfig.defaultLat && DSMapConfig.defaultLng && DSMapConfig.emptyZoom ) {
            this.centerAndZoom( new DSPoint( DSMapConfig.defaultLat, DSMapConfig.defaultLng ), DSMapConfig.emptyZoom );
        }
    }
}

/* Pan Controls */
/**
 * Pans north by 45% of the map height
 */
DSMap.prototype.panNorth = function() {
    this.slideBy( 0, 0.45 * this.containerHeight );
}
/**
 * Pans north by 45% of the map height and east by 45% of the map width
 */
DSMap.prototype.panNorthEast = function() {
    this.slideBy( -0.45 * this.containerWidth, 0.45 * this.containerHeight );
}
/**
 * Pans north by 45% of the map height and west by 45% of the map width
 */
DSMap.prototype.panNorthWest = function() {
    this.slideBy( 0.45 * this.containerWidth, 0.45 * this.containerHeight );
}
/**
 * Pans south by 45% of the map height
 */
DSMap.prototype.panSouth = function() {
    this.slideBy( 0, -0.45 * this.containerHeight );
}
/**
 * Pans south by 45% of the map height and east by 45% of the map width
 */
DSMap.prototype.panSouthEast = function() {
    this.slideBy( -0.45 * this.containerWidth, -0.45 * this.containerHeight );
}
/**
 * Pans south by 45% of the map height and west by 45% of the map width
 */
DSMap.prototype.panSouthWest = function() {
    this.slideBy( 0.45 * this.containerWidth, -0.45 * this.containerHeight );
}
/**
 * Pans east by 45% of the map width
 */
DSMap.prototype.panEast = function() {
    this.slideBy( -0.45 * this.containerWidth, 0 );
}
/**
 * Pans west by 45% of the map width
 */
DSMap.prototype.panWest = function() {
    this.slideBy( 0.45 * this.containerWidth, 0 );
}

/**
 * @param p A DSPoint to center the map on
 * @param z The desired zoom level
 */
DSMap.prototype.centerAndZoom = function( p, z ) {
    var o = {
        'previousZoomLevel': this.zoomLevel,
        'previousCenter': this.getCenterPoint(),
        'zoomLevel': z,
        'center': p
    };
    this.setZoomLevel( z, p );
    this.events.triggerEvent( 'recenter', o, this );
}
/* * */
DSMap.LARGE_CONTROLS = {
    'zoom': {
        'zoomIn': [ DSMapConfig.imageBase + 'map_zoom_in.png', 35, 96, 0, 24, 23, DSMap.prototype.zoomIn ],
        'zoomOut': [ DSMapConfig.imageBase + 'map_zoom_out.png', 35, 287, 0, 24, 22, DSMap.prototype.zoomOut ],
        'slider': [ DSMapConfig.imageBase + 'map_zoom_slider.png', 35, 121, 0, 24, 162 ],
        'thumb': [ DSMapConfig.imageBase + 'map_zoom_thumb.png', 31, 215, 1, 30, 17 ]
    },
    'pan': [
        [ DSMapConfig.imageBase + 'map_pan_n.png', 36, 10, 0, 19, 27, DSMap.prototype.panNorth ],
        [ DSMapConfig.imageBase + 'map_pan_ne.png', 55, 18, 0, 19, 19, DSMap.prototype.panNorthEast ],
        [ DSMapConfig.imageBase + 'map_pan_nw.png', 17, 18, 0, 19, 19, DSMap.prototype.panNorthWest ],
        [ DSMapConfig.imageBase + 'map_pan_s.png', 36, 56, 0, 19, 29, DSMap.prototype.panSouth ],
        [ DSMapConfig.imageBase + 'map_pan_se.png', 54, 56, 0, 19, 19, DSMap.prototype.panSouthEast ],
        [ DSMapConfig.imageBase + 'map_pan_sw.png', 19, 56, 0, 17, 17, DSMap.prototype.panSouthWest ],
        [ DSMapConfig.imageBase + 'map_pan_e.png', 55, 37, 0, 28, 19, DSMap.prototype.panEast ],
        [ DSMapConfig.imageBase + 'map_pan_w.png', 10, 37, 0 , 26, 19, DSMap.prototype.panWest ],
        [ DSMapConfig.imageBase + 'map_pan_center.png', 36, 37, 0, 19, 19, DSMap.prototype.bestFit ]
    ]
};
DSMap.prototype.bindEvent = function( elem, evt, obj, func ) {
    elem.addEventListener( evt, function( e ) { func.call( obj, e ); }, false );
}
/* * */
/**
 * Set the copyright notice on the map
 * The data vendor copyright notice cannot be changed
 *
 * @param copyrightStr The copyright notice to use
 */
DSMap.prototype.setCopyright = function( copyrightStr ) {
    this.copyrightLayer.firstChild.nodeValue = this.copyrightTxt = copyrightStr;
}

/* * */
function DSMap_GetScale() {
    return this.scale;
}
function DSMap_SetScale() {
    this.scale = DSMap.Scales[this.zoomLevel - 1];
    this.gridSystem = DSMap.Grid[this.zoomLevel - 1];
}

function DSMap_LoadTiles() {
    this.mlOffsetTop = this.mlOffsetLeft = 0;
    if( this.originPoint ) {
        var tl = this.gridSystem.getUL( this.originPoint.lng, this.originPoint.lat, this.mapLayerWidth, this.mapLayerHeight );
        var br = this.gridSystem.getLR( this.originPoint.lng, this.originPoint.lat, this.mapLayerWidth, this.mapLayerHeight );
        var tlTile = this.gridSystem.grid( tl );
        var brTile = this.gridSystem.grid( br );
        var o = 0;
        for( var i = tlTile.y; i < brTile.y; ++i ) {
            for( var j = tlTile.x; j < brTile.x; ++j ) {
                this.tiles[o].setGridXY( j, i );
                this.tiles[o].setZoomLevel( this.zoomLevel );
                this.tiles[o++].setUrl();
            }
        }
        this.updateMap();
        this.recenter();
        this.updateObjects();
    }
}

function DSMap_UpdateTiles() {
    for( var i = 0; i < this.tiles.length; ++i ) {
        this.tiles[i].setMapOffsetXY( this.mlOffsetLeft, this.mlOffsetTop );
        this.tiles[i].setX( i % this.columns );
        this.tiles[i].setY( Math.floor( i / this.columns ) );
        this.tiles[i].reset();
    }
}
function DSMap_UpdateObjects() {
    for( var it = new DSMapObject_Iterator( this.objects ); it.hasNext(); ) {
        this.positionObject( it.next() );
    }
}
function DSMap_UpdateMap() {
    this.updateTiles();
}
function DSMap_Recenter() {
    if( this.originPoint ) {
        var xy = this.getXYByPoint( this.originPoint );
        this.mapLayer.style.left = Math.round( ( this.containerWidth / 2 ) - xy.x ) + 'px';
        this.mapLayer.style.top = Math.round( ( this.containerHeight / 2 ) - xy.y  ) + 'px';
    }
}
DSMap.prototype.slide = function() {
    if( this.slideSteps.length ) {
        var p = this.slideSteps.shift();
        this.mapLayer.style.left = ( parseInt( this.mapLayer.style.left ) + p[0] ) + 'px';
        this.mapLayer.style.top = ( parseInt( this.mapLayer.style.top ) + p[1] ) + 'px';
        this.checkWrap();

        var o = this;
        this.slideId = window.setTimeout( function() { o.slide() }, DSMapConfig.slideDelay );
    }
}
DSMap.prototype.slideBy = function( x, y ) {
    if( this.slideId ) {
        window.clearTimeout( this.slideId );
    }

    this.slideSteps = new Array();

    var absX = Math.abs( x );
    var absY = Math.abs( y );

    var distance = absX > absY ? absX : absY;
    var steps = Math.floor( distance / DSMapConfig.slidePixels );

    var dx = dy = 0;
    if ( steps > 0 ) {
        dx = Math.floor( absX / steps ) * ( absX > 0 ? ( x / absX ) : 1 );
        dy = Math.floor( absY / steps ) * ( absY > 0 ? ( y / absY ) : 1 );
    }
    // TODO: this isn't quite right.  it pops too far and the end because of the math.floors
    var rx = x - ( dx * steps );
    var ry = y - ( dy * steps );

    while( steps-- ) {
        this.slideSteps.push( [ dx, dy ] );
    }
    if( rx != 0 || ry != 0 ) {
        this.slideSteps.push( [ rx, ry ] );
    }

    var o = this;
    this.slideId = window.setTimeout( function() { o.slide() }, DSMapConfig.slideDelay );
}
// panned too far north, take tiles from the bottom and put them on top
function DSMap_WrapNorth() {
    this.mlOffsetTop -= DSMapConfig.tileHeight;
    for( var i = 0; i < this.columns; ++i ) {
        var t = this.tiles.pop();
        t.addGridYOffset( -this.rows );
        this.tiles.unshift( t );
        t.setUrl();
    }
    this.updateMap ();
}
// panned too far south, take tiles from the top and put them on bottom
function DSMap_WrapSouth() {
    this.mlOffsetTop += DSMapConfig.tileHeight;
    for( var i = 0; i < this.columns; ++i ) {
        var t = this.tiles.shift();
        t.addGridYOffset( this.rows );
        this.tiles.push( t );
        t.setUrl();
    }
    this.updateMap ();
}
// panned too far west, take tiles from the right and put them on left
function DSMap_WrapWest() {
    this.mlOffsetLeft -= DSMapConfig.tileWidth;
    for( var i = this.rows - 1; i < this.tiles.length; i += this.rows ) {
        var t = this.tiles.splice( i, 1 )[0];
        t.addGridXOffset( -this.rows );
        this.tiles.splice( i - this.rows + 1, 0, t );
        t.setUrl();
    }
    this.updateMap ();
}
// panned too far east, take tiles from the left and put them on right
function DSMap_WrapEast() {
    this.mlOffsetLeft += DSMapConfig.tileWidth;
    for( var i = 0; i < this.tiles.length; i += this.rows ) {
        var t = this.tiles.splice( i, 1 )[0];
        t.addGridXOffset( this.rows );
        this.tiles.splice( i + this.rows - 1, 0, t );
        t.setUrl();
    }
    this.updateMap ();
}


function DSMap_CancelEvent( evt ) {
    var e = new DOM2Event( evt, window.event, this );
    e.stopPropagation();
    e.preventDefault();
    return false;
}

function DSMap_CenterOnPoint( p ) {
    var o = {
        'previousZoomLevel': this.zoomLevel,
        'previousCenter': this.getCenterPoint(),
        'zoomLevel': this.zoomLevel,
        'center': p
    };
    this.originPoint = new DSPoint( p.lat, p.lng );
    this.loadTiles();
    this.events.triggerEvent( 'recenter', o, this );
}
function DSMap_GetXYByPoint( p ) {
    if( this.tiles[0].gridX != null && this.tiles[0].gridY != null ) {
        var x = this.gridSystem.toX( this.tiles[0].gridX );
        var y = this.gridSystem.toY( this.tiles[0].gridY );
        var xy = this.gridSystem.toXY( p.lng, p.lat );
        return new coord( Math.round( xy.x - x ), Math.round( xy.y - y ) );
    }
    return null;
}
function DSMap_GetPointByXY( x, y ) {
    if( this.tiles[0].gridX != null && this.tiles[0].gridY != null ) {
        var xy = this.gridSystem.toLL( this.gridSystem.toXY( this.tiles[0].gridX, x, this.tiles[0].gridY, y ) );
        return new DSPoint( xy.y, xy.x )
    }
    return null;
}
function DSMap_GetCenterPoint() {
    var x = ( this.containerWidth / 2 ) - this.getMapLeft();
    var y = ( this.containerHeight / 2 ) - this.getMapTop();
    return this.getPointByXY( x, y );
}

function DSMap_CheckWrap() {
    var l = this.getMapLeft();
    var t = this.getMapTop();
    var w = this.mapLayerWidth;
    var h = this.mapLayerHeight;

    if( t > -( DSMapConfig.tileHeight / 2 ) ) {
        this.wrapNorth();
    } else if ( ( t + h - this.containerHeight ) < ( DSMapConfig.tileHeight / 2 ) ) {
        this.wrapSouth();
    }
    if( l > -( DSMapConfig.tileWidth / 2 ) ) {
        this.wrapWest();
    } else if ( ( l + w - this.containerWidth ) < ( DSMapConfig.tileWidth / 2 ) ) {
        this.wrapEast();
    }
}

/* Map Object Related methods */
DSMap.prototype.positionObject = function( obj ) {
    var xy = this.getXYByPoint( obj.point );
    if( xy ) {
        obj.element.style.left = ( xy.x - obj.xOffset + this.mlOffsetLeft ) + 'px';
        obj.element.style.top = ( xy.y - obj.yOffset + this.mlOffsetTop ) + 'px';
    }
}

/**
 * Add an object to the map
 *
 * @param obj A DSMapObject
 * @return An object id
 */
DSMap.prototype.addObject = function( obj ) {
    var id = this.objects.add( obj );
    this.mapLayer.appendChild( obj.element );
    obj.element.style.position = 'absolute';
    obj.z = 5 + obj.zOffset;
    obj.element.style.zIndex = obj.z;
    this.positionObject( obj );
    return id;
}
/**
 * Remove an object from the map
 *
 * @param id The id of the object to be removed
 * @return true if an object was removed, false if not
 */
DSMap.prototype.removeObject = function( id ) {
    var obj = this.objects.getById( id );
    if( obj ) {
        this.mapLayer.removeChild( obj.element );
        this.objects.remove( id );
        return true;
    }
    return false;
}

/**
 * Remove all objects from the map
 *
 * @param obj A DSMapObject
 */
DSMap.prototype.removeAll = function() {
    var o;
    for( var it = new DSMapObject_Iterator( this.objects ); it.hasNext(); ) {
        o = it.next();
        o.element.parentNode.removeChild( o.element );
    }
    this.objects.removeAll();
}

function DSMap_ShowObjects() {
    for( var it = new DSMapObject_Iterator( this.objects ); it.hasNext(); ) {
        var o = it.next();
	if (o.element.whenHiddenState)
	{
		o.element.style.visibility = o.element.whenHiddenState;
	}	   
	else
	{
		o.element.style.visibility = 'visible';
	}
    }
}
function DSMap_HideObjects() {
    for( var it = new DSMapObject_Iterator( this.objects ); it.hasNext(); ) {
        var o = it.next();
        o.element.whenHiddenState = o.element.style.visibility;
        o.element.style.visibility = 'hidden';
    }
}

function DSMap_MouseDown( evt ) {
    var e = new DOM2Event( evt, window.event, this );
    if( this.draggingEnabled && e.button == 0 ) {
        this.prevX = e.clientX;
        this.prevY = e.clientY;
        this.drag = true;
        this.viewport.style.cursor = 'move';
    }
    this.previousCenter = this.getCenterPoint();
    this.previouszoom = this.zoomLevel;
    e.preventDefault();
    e.stopPropagation();
}
function DSMap_MouseUp( evt ) {
    if( this.zoomSlider && this.zoomSlider.drag ) {
        var percent = this.zoomSlider.mouseUp();
        this.setZoomLevel( Math.round( percent * ( DSMap.Scales.length - 1 ) ) + 1, this.zoomSlider.mapCenter );
    }
    this.viewport.style.cursor = 'auto';
    this.prevX = this.prevY = null;
    this.drag = false;
    var o = {
        'zoomLevel': this.zoomLevel,
        'center': this.getCenterPoint(),
        'previousZoomLevel': this.previousZoom,
        'previousCenter': this.previousCenter
    };
    this.previousCenter = this.previousZoom = null;
    this.events.triggerEvent( 'recenter', o, this );
}
function DSMap_MouseMove( evt ) {
    var e = new DOM2Event( evt, window.event, this );
    if( this.zoomSlider && this.zoomSlider.drag ) {
        var p = this.zoomSlider.mouseMove( e );
        var lvl =  ( p * ( DSMap.Scales.length - 1 ) ) + 1;
        var intLvl = Math.round( lvl + .5 );
        var decLvl = lvl - intLvl;
        var nl = intLvl - 1 > 0 ? intLvl - 1 : 1;

        var ns = DSMap.Scales[intLvl - 1] + ( (  DSMap.Scales[intLvl - 1] - DSMap.Scales[nl - 1] ) * decLvl );
        if( ns > 0 ) {
            var r = this.scale / ns;
            var th = Math.round( DSMapConfig.tileHeight * r );
            var tw = Math.round( DSMapConfig.tileWidth * r );
            var ol = ( ( Math.abs( this.zoomSlider.zoomLeftAdjusted ) + ( this.containerWidth / 2 ) ) / DSMapConfig.tileWidth );
            var ot = ( ( Math.abs( this.zoomSlider.zoomTopAdjusted ) + ( this.containerHeight / 2 ) ) / DSMapConfig.tileHeight );
            this.mapLayer.style.left = Math.round( this.zoomSlider.zoomLeft + ( ( DSMapConfig.tileWidth - tw ) * ol ) ) + 'px';
            this.mapLayer.style.top = Math.round( this.zoomSlider.zoomTop + ( ( DSMapConfig.tileHeight - th ) * ot ) ) + 'px';
            for( var i = 0; i < this.tiles.length; ++i ) {
                this.tiles[i].setSize( tw, th );
                this.tiles[i].position();
            }
        }
    } else if( this.drag && this.prevX != null && this.prevY != null ) {
        var deltaX = this.prevX - e.clientX;
        var deltaY = this.prevY - e.clientY;
        this.mapLayer.style.left = ( parseInt( this.mapLayer.style.left ) - deltaX ) + 'px';
        this.mapLayer.style.top = ( parseInt( this.mapLayer.style.top ) - deltaY ) + 'px';
        this.prevX = e.clientX;
        this.prevY = e.clientY;
        this.checkWrap();
    }
    e.preventDefault();
    e.stopPropagation();
}

function DSMap_DoubleClick( evt ) {
    var e = new DOM2Event( evt, window.event, this );
    var xy = DSMap.GetOffsets( this.viewport );
    this.slideBy( ( this.containerWidth / 2 ) - ( e.pageX - xy.x ), ( this.containerHeight / 2 ) - ( e.pageY - xy.y ) );
}

/*
 *
 */
function DSMap_AddCopyright() {
    this.copyrightLayer = DSMap.createElement( this.viewport, 'div', this.copyrightTxt, 'style', 'position: absolute; left: 0; z-index: 100; font-size: 10px; bottom: ' +  ( this.containerWidth >= 250 ? 0 : '10px' ) );
    this.dataLayer = DSMap.createElement( this.viewport, 'div', '\xa92005 NAVTEQ', 'style', 'position: absolute; bottom: 0; z-index: 100; font-size: 10px; ' + ( this.containerWidth >= 250 ? 'right' : 'left' ) + ': 0' );
}

function DSMap_GetMapLeft() {
    return parseInt( this.mapLayer.style.left ) + this.mlOffsetLeft;
}
function DSMap_GetMapTop() {
    return parseInt( this.mapLayer.style.top ) + this.mlOffsetTop;
}

DSMap.prototype.initContainer = function() {
    this.viewport = DSMap.createElement( this.container, 'div', '', 'style', 'overflow: hidden; position: relative; background-color: ' + DSMapConfig.background + '; width: ' + this.containerWidth + 'px; height: ' + this.containerHeight + 'px' );
    this.bindEvent( this.viewport, 'mousedown', this, this.mouseDown );

    this.mapLayer = DSMap.createElement( this.viewport, 'div', '', 'style', 'position: absolute; top: 0; left: 0; z-index: 1; width: ' + this.mapLayerWidth + 'px; height: ' + this.mapLayerHeight + 'px'  );
    this.tileLayer = DSMap.createElement( this.mapLayer, 'div', '', 'style', 'position: absolute; top: 0; left: 0; z-index: 1; width: 100%; height: 100%' );

    this.tiles = new Array();
    for( var i = 0; i < this.rows; ++i ) {
        for( var j = 0; j < this.columns; ++j ) {
            this.tiles.push( new DSTile( this.tileLayer, j, i, 1, this ) );
        }
    }
    this.mlOffsetTop = this.mlOffsetLeft = 0;
    this.copyrightTxt = '\xa92005 Local Matters, Inc.';
    this.addCopyright();
}

DSMap.Scales = [ 1.7021276, 3.4042553, 6.80851064, 13.61702128, 27.23404256, 54.46808512, 108.93617024, 217.87234048, 435.74468096, 871.48936192, 1742.97872384, 3485.95744768, 6971.9148953, 13943.82979072 ]
//DSMap.Scales = [ 3.4042553, 6.80851064, 13.61702128, 27.23404256, 54.46808512, 108.93617024, 217.87234048, 435.74468096, 871.48936192, 1742.97872384, 3485.95744768, 6971.9148953, 13943.82979072 ]
DSMap.Grid = new Array();
DSMap.SetScales = function() {
    for (var i = 0; i < DSMap.Scales.length; ++i )
        DSMap.Grid[i] = GridSystem.newInstance( DSMap.Scales[i] * DSMapConfig.tileWidth, DSMap.Scales[i] * DSMapConfig.tileHeight, DSMapConfig.tileWidth, DSMapConfig.tileHeight );
}
DSMap.SetScales();

DSMap.prototype.mouseDown = DSMap_MouseDown;
DSMap.prototype.mouseUp = DSMap_MouseUp;
DSMap.prototype.mouseMove = DSMap_MouseMove;
DSMap.prototype.doubleClick = DSMap_DoubleClick;

DSMap.prototype.addCopyright = DSMap_AddCopyright;
DSMap.prototype.loadTiles = DSMap_LoadTiles;
DSMap.prototype.getScale = DSMap_GetScale;
DSMap.prototype.setScale = DSMap_SetScale;
DSMap.prototype.centerOnPoint = DSMap_CenterOnPoint;
DSMap.prototype.updateObjects = DSMap_UpdateObjects;
DSMap.prototype.updateTiles = DSMap_UpdateTiles;
DSMap.prototype.updateMap = DSMap_UpdateMap;
DSMap.prototype.recenter = DSMap_Recenter;

DSMap.prototype.hideObjects = DSMap_HideObjects;
DSMap.prototype.showObjects = DSMap_ShowObjects;

DSMap.prototype.getPointByXY = DSMap_GetPointByXY;
DSMap.prototype.getXYByPoint = DSMap_GetXYByPoint;
DSMap.prototype.getCenterPoint = DSMap_GetCenterPoint;
DSMap.prototype.wrapNorth = DSMap_WrapNorth;
DSMap.prototype.wrapSouth = DSMap_WrapSouth;
DSMap.prototype.wrapWest = DSMap_WrapWest;
DSMap.prototype.wrapEast = DSMap_WrapEast;
DSMap.prototype.checkWrap = DSMap_CheckWrap;

DSMap.prototype.getMapLeft = DSMap_GetMapLeft;
DSMap.prototype.getMapTop = DSMap_GetMapTop;
/*
* DS map object clases
* mapobj.js,v 1.23 2005/09/27 23:15:31 jvinding Exp
*/

/* properties */
function DSMapObject_SetProperty( name, val ) {
    return this.properties[name] = val;
}
function DSMapObject_SetProperties() {
    for( var i=0; i<arguments.length; i++ ) {
        this.properties[arguments[i]] = arguments[++i];
    }
    return this;
}
function DSMapObject_GetProperty( name ) {
    return( name && this.properties[name] ? this.properties[name] : "" );
}

function DSMapObject_Init( point, element, xOffset, yOffset, zOffset ) {
    if( arguments.length > 0 ) {
        this.point = point;
        this.element = element;
        this.xOffset = ( xOffset ? xOffset : 0 );
        this.yOffset = ( yOffset ? yOffset : 0 );
        this.zOffset = ( zOffset ? zOffset : 0 );
    }
    this.properties = new Object();
}

function DSMapObject( point, element, xOffset, yOffset, zOffset ) {
    this.init( point, element, xOffset, yOffset, zOffset );
}
DSMapObject.prototype.init = DSMapObject_Init;
DSMapObject.prototype.setProperty = DSMapObject_SetProperty;
DSMapObject.prototype.getProperty = DSMapObject_GetProperty;
DSMapObject.prototype.setProperties = DSMapObject_SetProperties;


/* MapObject collection */
function DSMapObject_Iterator( coll, offset ) {
    this.coll = coll;
    this.pos = ( offset ? offset : 0 );
}
function DSMapObject_HasNext() {
    if( ( this.pos+1 ) <= this.coll.order.length ) {
        return true;
    }
    this.pos = 0; // reset
    return false;
}
function DSMapObject_Next() {
    return( this.coll.objects[this.coll.order[this.pos++]] );
}
function DSMapObject_SetIteratorOffset( offset ) {
    this.pos = offset;
}
function DSMapObject_GetIteratorOffset() {
    return this.pos;
}


function DSMapObject_Add( obj ) {
    this.objects[this.currIdx] = obj;
    this.order.push( this.currIdx );
    return this.currIdx++;
}
function DSMapObject_Remove( key ) {
    if( this.objects[key] ) {
        delete this.objects[key]
        for( var i=0; i<this.order.length; ++i ) {
            if( this.order[i] == key ) {
                this.order.splice( i, 1 );
            }
        }
    }
}
function DSMapObject_RemoveAll() {
    this.objects = new Object();
    this.order = new Array();
    this.currPos = this.currIdx = 0;
}
function DSMapObject_GetByIndex( i ) {
    return this.objects[this.order[i]];
}
function DSMapObject_GetById( id ) {
    return this.objects[id] ? this.objects[id] : null;
}
function DSMapObject_Size() {
    return this.order.length;
}
function DSMapObject_GetByProperty( name, val ) {
    for( var i=0; i<this.order.length; ++i ) {
        if( this.objects[this.order[i]].getProperty( name ) == val ) {
            return this.objects[this.order[i]];
        }
    }
    return null;
}
function DSMapObject_GetIdsByProperty( name, val ) {
    var ids = new Array();
    for( var i=0; i<this.order.length; ++i ) {
        if( this.objects[this.order[i]].getProperty( name ) == val ) {
            ids.push( this.order[i] );
        }
    }
    return ids;
}
function DSMapObject_UpdateAll() {
    var o;
    var i=0;

    // reset existing collisions
    for( var it = new DSMapObject_Iterator( map.objects ); it.hasNext(); ) {
        o = it.next();
        if( o instanceof DSPoi && o.collisions ) {
            o.collisions.removeAll();
        }
    }

    // update all pois
    for( it = new DSMapObject_Iterator( map.objects ); it.hasNext(); ) {
        o = it.next();
        if( o instanceof DSPoi && typeof o.update == 'function' ) {
            o.update( i );
        }
        ++i; // outside the if!
    }
}

function DSMapObject_Collection() {
    this.objects = new Object();
    this.order = new Array();
    this.currPos = this.currIdx = 0;
}
DSMapObject_Collection.prototype.add    = DSMapObject_Add;
DSMapObject_Collection.prototype.remove = DSMapObject_Remove;
DSMapObject_Collection.prototype.getById = DSMapObject_GetById;
DSMapObject_Collection.prototype.getByIndex = DSMapObject_GetByIndex;
DSMapObject_Collection.prototype.size = DSMapObject_Size;
DSMapObject_Collection.prototype.getByProperty = DSMapObject_GetByProperty;
DSMapObject_Collection.prototype.getIdsByProperty = DSMapObject_GetIdsByProperty;
DSMapObject_Collection.prototype.removeAll = DSMapObject_RemoveAll;
DSMapObject_Collection.prototype.updateAll = DSMapObject_UpdateAll;

DSMapObject_Iterator.prototype.next    = DSMapObject_Next;
DSMapObject_Iterator.prototype.hasNext = DSMapObject_HasNext;
DSMapObject_Iterator.prototype.setOffset = DSMapObject_SetIteratorOffset;
DSMapObject_Iterator.prototype.getOffset = DSMapObject_GetIteratorOffset;

/*
 * DD functions
 * 1.2
 */

function DSEvent() {
    this.events = {};
}
DSEvent.prototype.registerEvent = function( type ) {
    if( typeof this.events[type] != 'undefined' ) {
        alert( 'Attempt to re-register event type: ' + type );
    } else {
        this.events[type] = [];
    }
}
DSEvent.prototype.triggerEvent = function( type, evt, obj ) {
    if( typeof this.events[type] == 'undefined' ) {
        alert( 'Unknown event: ' + type );
        return false;
    }
    if( typeof evt != 'object' || evt == null ) {
        evt = {};
    }
    evt.eventType = type;

    for( var i = 0; i < this.events[type].length; ++i ) {
        if( this.events[type][i]( evt, obj ) == false ) {
            return false;
        }
    }
    return true;
}

DSEvent.prototype.addListener = function( type, callback ) {
    if( typeof this.events[type] == 'undefined' ) {
        alert( 'attempt to listen to unknown event type: ' + type );
    } else {
        this.events[type].push( callback );
    }
}
DSEvent.prototype.getListeners = function( type ) {
    return this.events[type];
}