/*  Prototype JavaScript framework, version 1.4.0_pre11
 *  (c) 2005 Sam Stephenson <sam@conio.net>
 *
 *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
 *  against the source tree, available from the Prototype darcs repository. 
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *
 *  For details, see the Prototype web site: http://prototype.conio.net/
 
/*--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.4.0_pre11',
  
  emptyFunction: function() {},
  K: function(x) {return x}
};

var Class = {
  create: function() {
    return function() { 
      this.initialize.apply(this, arguments);
    }
  }
};

var Abstract = new Object();

Object.extend = function(destination, source) {
  for (property in source) {
    destination[property] = source[property];
  }
  return destination;
};

Object.inspect = function(object) {
  try {
    if (object == undefined) return 'undefined';
    if (object == null) return 'null';
    return object.inspect ? object.inspect() : object.toString();
  } catch (e) {
    if (e instanceof RangeError) return '...';
    throw e;
  }
};

Function.prototype.bind = function(object) {
  var __method = this;
  return function() {
    return __method.apply(object, arguments);
  }
};

Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    return __method.call(object, event || window.event);
  }
};

Object.extend(Number.prototype, {
  toColorPart: function() {
    var digits = this.toString(16);
    if (this < 16) return '0' + digits;
    return digits;
  },

  succ: function() {
    return this + 1;
  },
  
  times: function(iterator) {
    $R(0, this, true).each(iterator);
    return this;
  }
});

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0; i < arguments.length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
  }
};



function $() {
  var elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string')
      element = document.getElementById(element);

    if (arguments.length == 1) 
      return element;

    elements.push(element);
  }

  return elements;
};

Object.extend(String.prototype, {
  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  escapeHTML: function() {
    var div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return div.innerHTML;
  },

  unescapeHTML: function() {
    var div = document.createElement('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
  },
  
  toQueryParams: function() {
    var pairs = this.match(/^\??(.*)$/)[1].split('&');
    return pairs.inject({}, function(params, pairString) {
      var pair = pairString.split('=');
      params[pair[0]] = pair[1];
      return params;
    });
  },
  
  inspect: function() {
    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
  }
});

String.prototype.parseQuery = String.prototype.toQueryParams;


var $break    = new Object();
var $continue = new Object();

var Enumerable = {
  each: function(iterator) {
    var index = 0;
    try {
      this._each(function(value) {
        try {
          iterator(value, index++);
        } catch (e) {
          if (e != $continue) throw e;
        }
      });
    } catch (e) {
      if (e != $break) throw e;
    }
  },
  
  all: function(iterator) {
    var result = true;
    this.each(function(value, index) {
      if (!(result &= (iterator || Prototype.K)(value, index))) 
        throw $break;
    });
    return result;
  },
  
  any: function(iterator) {
    var result = true;
    this.each(function(value, index) {
      if (result &= (iterator || Prototype.K)(value, index)) 
        throw $break;
    });
    return result;
  },
  
  collect: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      results.push(iterator(value, index));
    });
    return results;
  },
  
  detect: function (iterator) {
    var result;
    this.each(function(value, index) {
      if (iterator(value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },
  
  findAll: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (iterator(value, index))
        results.push(value);
    });
    return results;
  },
  
  grep: function(pattern, iterator) {
    var results = [];
    this.each(function(value, index) {
      var stringValue = value.toString();
      if (stringValue.match(pattern))
        results.push((iterator || Prototype.K)(value, index));
    });
    return results;
  },
  
  include: function(object) {
    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },
  
  inject: function(memo, iterator) {
    this.each(function(value, index) {
      memo = iterator(memo, value, index);
    });
    return memo;
  },
  
  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.collect(function(value) {
      return value[method].apply(value, args);
    });
  },
  
  max: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (value >= (result || value))
        result = value;
    });
    return result;
  },
  
  min: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (value <= (result || value))
        result = value;
    });
    return result;
  },
  
  partition: function(iterator) {
    var trues = [], falses = [];
    this.each(function(value, index) {
      ((iterator || Prototype.K)(value, index) ? 
        trues : falses).push(value);
    });
    return [trues, falses];
  },
  
  pluck: function(property) {
    var results = [];
    this.each(function(value, index) {
      results.push(value[property]);
    });
    return results;
  },
  
  reject: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator(value, index))
        results.push(value);
    });
    return results;
  },
  
  sortBy: function(iterator) {
    return this.collect(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },
  
  toArray: function() {
    return this.collect(Prototype.K);
  },
  
  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (typeof args.last() == 'function')
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      iterator(value = collections.pluck(index));
      return value;
    });
  },
  
  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray
});

var $A = Array.from = function(iterable) {
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0; i < iterable.length; i++) results.push(iterable[i]);
    return results;
  }
};

Object.extend(Array.prototype, Enumerable);

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0; i < this.length; i++)
      iterator(this[i]);
  },
  
  first: function() {
    return this[0];
  },
  
  last: function() {
    return this[this.length - 1];
  },
  
  compact: function() {
    return this.select(function(value) {
      return value != undefined || value != null;
    });
  },
  
  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(value.constructor == Array ?
        value.flatten() : [value]);
    });
  },
  
  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },
  
  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  }
});

var Hash = {
  _each: function(iterator) {
    for (key in this) {
      var value = this[key];
      if (typeof value == 'function') continue;
      
      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },
  
  keys: function() {
    return this.pluck('key');
  },
  
  values: function() {
    return this.pluck('value');
  },
  
  merge: function(hash) {
    return $H(hash).inject($H(this), function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },
  
  toQueryString: function() {
    return this.map(function(pair) {
      return pair.map(encodeURIComponent).join('=');
    }).join('&');
  },
  
  inspect: function() {
    return '#<Hash:{' + this.map(function(pair) {
      return pair.map(Object.inspect).join(': ');
    }).join(', ') + '}>';
  }
};

function $H(object) {
  var hash = Object.extend({}, object || {});
  Object.extend(hash, Enumerable);
  Object.extend(hash, Hash);
  return hash;
} document.getElementsByClassName = function(className, parentElement) {
  var children = (document.body || $(parentElement)).getElementsByTagName('*');
  return $A(children).inject([], function(elements, child) {
    if (Element.hasClassName(child, className))
      elements.push(child);
    return elements;
  });
};

if (!window.Element) {
  var Element = new Object();
};

Object.extend(Element, {
  visible: function(element) {
    return $(element).style.display != 'none';
  },
  
  toggle: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      Element[Element.visible(element) ? 'show' : 'hide'](element);
    }
  },

  hide: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = 'none';
    }
  },
  
  show: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = '';
    }
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
  },
  
  getHeight: function(element) {
    element = $(element);
    return element.offsetHeight; 
  },
  
  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).include(className);
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).add(className);
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).remove(className);
  },
  
  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    for (var i = 0; i < element.childNodes.length; i++) {
      var node = element.childNodes[i];
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 
        Element.remove(node);
    }
  },
  
  empty: function(element) {
    return $(element).innerHTML.match(/^\s*$/);
  },
  
  scrollTo: function(element) {
    element = $(element);
    var x = element.x ? element.x : element.offsetLeft,
        y = element.y ? element.y : element.offsetTop;
    window.scrollTo(x, y);
  }
});

var Toggle = new Object();
Toggle.display = Element.toggle;

var Field = {
  clear: function() {
    for (var i = 0; i < arguments.length; i++)
      $(arguments[i]).value = '';
  },

  focus: function(element) {
    $(element).focus();
  },
  
  present: function() {
    for (var i = 0; i < arguments.length; i++)
      if ($(arguments[i]).value == '') return false;
    return true;
  },
  
  select: function(element) {
    $(element).select();
  },
   
  activate: function(element) {
    $(element).focus();
    $(element).select();
  }
};
var Form = {
  serialize: function(form) {
    var elements = Form.getElements($(form));
    var queryComponents = new Array();
    
    for (var i = 0; i < elements.length; i++) {
      var queryComponent = Form.Element.serialize(elements[i]);
      if (queryComponent)
        queryComponents.push(queryComponent);
    }
    
    return queryComponents.join('&');
  },
  
  getElements: function(form) {
    var form = $(form);
    var elements = new Array();

    for (tagName in Form.Element.Serializers) {
      var tagElements = form.getElementsByTagName(tagName);
      for (var j = 0; j < tagElements.length; j++)
        elements.push(tagElements[j]);
    }
    return elements;
  },
  
  getInputs: function(form, typeName, name) {
    var form = $(form);
    var inputs = form.getElementsByTagName('input');
    
    if (!typeName && !name)
      return inputs;
      
    var matchingInputs = new Array();
    for (var i = 0; i < inputs.length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) ||
          (name && input.name != name)) 
        continue;
      matchingInputs.push(input);
    }

    return matchingInputs;
  },

  disable: function(form) {
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      element.blur();
      element.disabled = 'true';
    }
  },

  enable: function(form) {
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      element.disabled = '';
    }
  },

  focusFirstElement: function(form) {
    var form = $(form);
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      if (element.type != 'hidden' && !element.disabled) {
        Field.activate(element);
        break;
      }
    }
  },

  reset: function(form) {
    $(form).reset();
  }
};

Form.Element = {
  serialize: function(element) {
    var element = $(element);
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);
    
    if (parameter)
      return encodeURIComponent(parameter[0]) + '=' + 
        encodeURIComponent(parameter[1]);                   
  },
  
  getValue: function(element) {
    var element = $(element);
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);
    
    if (parameter) 
      return parameter[1];
  }
};

Form.Element.Serializers = {
  input: function(element) {
    switch (element.type.toLowerCase()) {
      case 'submit':
      case 'hidden':
      case 'password':
      case 'text':
        return Form.Element.Serializers.textarea(element);
      case 'checkbox':  
      case 'radio':
        return Form.Element.Serializers.inputSelector(element);
    }
    return false;
  },

  inputSelector: function(element) {
    if (element.checked)
      return [element.name, element.value];
  },

  textarea: function(element) {
    return [element.name, element.value];
  },
  
  select: function(element) {
    return Form.Element.Serializers[element.type == 'select-one' ? 
      'selectOne' : 'selectMany'](element);
  },
  
  selectOne: function(element) {
    var value = '', opt, index = element.selectedIndex;
    if (index >= 0) {
      opt = element.options[index];
      value = opt.value;
      if (!value && !('value' in opt))
        value = opt.text;
    }
    return [element.name, value];
  },
  
  selectMany: function(element) {
    var value = new Array();
    for (var i = 0; i < element.length; i++) {
      var opt = element.options[i];
      if (opt.selected) {
        var optValue = opt.value;
        if (!optValue && !('value' in opt))
          optValue = opt.text;
        value.push(optValue);
      }
    }
    return [element.name, value];
  }
};
var $F = Form.Element.getValue;
Abstract.TimedObserver = function() {};
Abstract.TimedObserver.prototype = {
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;
    
    this.lastValue = this.getValue();
    this.registerCallback();
  },
  
  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },
  
  onTimerEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
};

Form.Element.Observer = Class.create();
Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  } });

if (!window.Event) {
  var Event = new Object();
}

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,

  element: function(event) {
    return event.target || event.srcElement;
  },

  isLeftClick: function(event) {
    return (((event.which) && (event.which == 1)) ||
            ((event.button) && (event.button == 1)));
  },

  pointerX: function(event) {
    return event.pageX || (event.clientX + 
      (document.documentElement.scrollLeft || document.body.scrollLeft));
  },

  pointerY: function(event) {
    return event.pageY || (event.clientY + 
      (document.documentElement.scrollTop || document.body.scrollTop));
  },

  stop: function(event) {
    if (event.preventDefault) { 
      event.preventDefault(); 
      event.stopPropagation(); 
    } else {
      event.returnValue = false;
      event.cancelBubble = true;
    }
  },

  findElement: function(event, tagName) {
    var element = Event.element(event);
    while (element.parentNode && (!element.tagName ||
        (element.tagName.toUpperCase() != tagName.toUpperCase())))
      element = element.parentNode;
    return element;
  },

  observers: false,
  
  _observeAndCache: function(element, name, observer, useCapture) {
    if (!this.observers) this.observers = [];
    if (element.addEventListener) {
      this.observers.push([element, name, observer, useCapture]);
      element.addEventListener(name, observer, useCapture);
    } else if (element.attachEvent) {
      this.observers.push([element, name, observer, useCapture]);
      element.attachEvent('on' + name, observer);
    }
  },
  
  unloadCache: function() {
    if (!Event.observers) return;
    for (var i = 0; i < Event.observers.length; i++) {
      Event.stopObserving.apply(this, Event.observers[i]);
      Event.observers[i][0] = null;
    }
    Event.observers = false;
  },

  observe: function(element, name, observer, useCapture) {
    var element = $(element);
    useCapture = useCapture || false;
    
    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.attachEvent))
      name = 'keydown';
    
    this._observeAndCache(element, name, observer, useCapture);
  },

  stopObserving: function(element, name, observer, useCapture) {
    var element = $(element);
    useCapture = useCapture || false;
    
    if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent)) name = 'keydown';
    
    if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element.detachEvent) { element.detachEvent('on' + name, observer); } } });

Event.observe(window, 'unload', Event.unloadCache, false);

var Position = {

  includeScrollOffsets: false, 

  prepare: function() {
    this.deltaX =  window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
    this.deltaY =  window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
  },

  realOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0; 
      element = element.parentNode;
    } while (element);
    return [valueL, valueT];
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return [valueL, valueT];
  },

  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = this.cumulativeOffset(element);

    return (y >= this.offset[1] && y <  this.offset[1] + element.offsetHeight && x >= this.offset[0] && x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = this.realOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = this.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] && this.ycomp <  this.offset[1] + element.offsetHeight && this.xcomp >= this.offset[0] && this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  overlap: function(mode, element) {  
    if (!mode) return 0;  
    if (mode == 'vertical') return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight;
    if (mode == 'horizontal') return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth;
  },

  clone: function(source, target) {
    source = $(source);
    target = $(target);
    target.style.position = 'absolute';
    var offsets = this.cumulativeOffset(source);
    target.style.top    = offsets[1] + 'px';
    target.style.left   = offsets[0] + 'px';
    target.style.width  = source.offsetWidth + 'px';
    target.style.height = source.offsetHeight + 'px';
  } };
// 
// Element.Class part Copyright (c) 2005 by Rick Olson
// 
// See scriptaculous.js for full license.

var Droppables = {
  drops: [],

  remove: function(element) {
    this.drops = this.drops.reject(function(d) { return d.element==element });
  },

  add: function(element) {
    element = $(element);
    var options = Object.extend({
      greedy:     true,
      hoverclass: null  
    }, arguments[1] || {});

    // cache containers
    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if((typeof containment == 'object') && 
        (containment.constructor == Array)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }

    Element.makePositioned(element); // fix IE
    options.element = element;

    this.drops.push(options);
  },

  isContained: function(element, drop) {
    var parentNode = element.parentNode;
    return drop._containers.detect(function(c) { return parentNode == c });
  },

  isAffected: function(pX, pY, element, drop) {
    return (
      (drop.element!=element) &&
      ((!drop._containers) ||
        this.isContained(element, drop)) &&
      ((!drop.accept) ||
        (Element.Class.has_any(element, drop.accept))) &&
      Position.within(drop.element, pX, pY) );
  },

  deactivate: function(drop) {
    if(drop.hoverclass)
      Element.Class.remove(drop.element, drop.hoverclass);
    this.last_active = null;
  },

  activate: function(drop) {
    if(this.last_active) this.deactivate(this.last_active);
    if(drop.hoverclass)
      Element.Class.add(drop.element, drop.hoverclass);
    this.last_active = drop;
  },

  show: function(event, element) {
    if(!this.drops.length) return;
    var pX = Event.pointerX(event);
    var pY = Event.pointerY(event);
    Position.prepare();

    var i = this.drops.length-1; do {
      var drop = this.drops[i];
      if(this.isAffected(pX, pY, element, drop)) {
        if(drop.onHover)
           drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
        if(drop.greedy) { 
          this.activate(drop);
          return;
        }
      }
    } while (i--);
    
    if(this.last_active) this.deactivate(this.last_active);
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
      if (this.last_active.onDrop) 
        this.last_active.onDrop(element, this.last_active.element, event);
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
};

var Draggables = {
  observers: [],
  addObserver: function(observer) {
    this.observers.push(observer);    
  },
  removeObserver: function(element) {  // element instead of obsever fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
  },
  notify: function(eventName, draggable) {  // 'onStart', 'onEnd'
    this.observers.invoke(eventName, draggable);
  }
};


var Draggable = Class.create();
Draggable.prototype = {
  initialize: function(element) {
    var options = Object.extend({
      handle: false,
      starteffect: function(element) { 
      },
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
      },
      endeffect: function(element) { 
      },
     // zindex: 1000,
      revert: false
    }, arguments[1] || {});

    this.element      = $(element);
    if(options.handle && (typeof options.handle == 'string'))
      this.handle = Element.Class.childrenWith(this.element, options.handle)[0];
      
    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;

    Element.makePositioned(this.element); // fix IE    

    this.offsetX      = 0;
    this.offsetY      = 0;
    this.originalLeft = this.currentLeft();
    this.originalTop  = this.currentTop();
    this.originalX    = this.element.offsetLeft;
    this.originalY    = this.element.offsetTop;

    this.options      = options;

    this.active       = false;
    this.dragging     = false;   

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);
    this.eventKeypress  = this.keyPress.bindAsEventListener(this);
    
    this.registerEvents();
  },
  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    this.unregisterEvents();
  },
  registerEvents: function() {
    Event.observe(document, "mouseup", this.eventMouseUp);
    Event.observe(document, "mousemove", this.eventMouseMove);
    Event.observe(document, "keypress", this.eventKeypress);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);
  },
  unregisterEvents: function() {
    //if(!this.active) return;
    //Event.stopObserving(document, "mouseup", this.eventMouseUp);
    //Event.stopObserving(document, "mousemove", this.eventMouseMove);
    //Event.stopObserving(document, "keypress", this.eventKeypress);
  },
  currentLeft: function() {
    return parseInt(this.element.style.left || '0');
  },
  currentTop: function() {
    return parseInt(this.element.style.top || '0')
  },
  startDrag: function(event) {
    if(Event.isLeftClick(event)) {
      
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if(src.tagName && (
        src.tagName=='INPUT' ||
        src.tagName=='SELECT' ||
        src.tagName=='BUTTON' ||
        src.tagName=='TEXTAREA')) return;
      
      // this.registerEvents();
      this.active = true;
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var offsets = Position.cumulativeOffset(this.element);
      this.offsetX =  (pointer[0] - offsets[0]);
      this.offsetY =  (pointer[1] - offsets[1]);
      Event.stop(event);
    }
  },
  finishDrag: function(event, success) {
    // this.unregisterEvents();

    this.active = false;
    this.dragging = false;

    if(this.options.ghosting) {
      Position.relativize(this.element);
      Element.remove(this._clone);
      this._clone = null;
    }

    if(success) Droppables.fire(event, this.element);
    Draggables.notify('onEnd', this);

    var revert = this.options.revert;
    if(revert && typeof revert == 'function') revert = revert(this.element);

    if(revert && this.options.reverteffect) {
      this.options.reverteffect(this.element, 
      this.currentTop()-this.originalTop,
      this.currentLeft()-this.originalLeft);
    } else {
      this.originalLeft = this.currentLeft();
      this.originalTop  = this.currentTop();
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect) 
      this.options.endeffect(this.element);


    Droppables.reset();
  },
  keyPress: function(event) {
    if(this.active) {
      if(event.keyCode==Event.KEY_ESC) {
        this.finishDrag(event, false);
        Event.stop(event);
      }
    }
  },
  endDrag: function(event) {
    if(this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.element);
    offsets[0] -= this.currentLeft();
    offsets[1] -= this.currentTop();
    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = (pointer[1] - offsets[1] - this.offsetY) + "px";
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },
  update: function(event) {
   if(this.active) {
      if(!this.dragging) {
        var style = this.element.style;
        this.dragging = true;
        
        if(Element.getStyle(this.element,'position')=='') 
          style.position = "relative";
        
        if(this.options.zindex) {
          this.options.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
          style.zIndex = this.options.zindex;
        }

        if(this.options.ghosting) {
          this._clone = this.element.cloneNode(true);
          Position.absolutize(this.element);
          this.element.parentNode.insertBefore(this._clone, this.element);
        }

        Draggables.notify('onStart', this);
        if(this.options.starteffect) this.options.starteffect(this.element);
      }

      Droppables.show(event, this.element);
      this.draw(event);
      if(this.options.change) this.options.change(this);

      // fix AppleWebKit rendering
      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 

      Event.stop(event);
   }
  }
};


String.prototype.camelize = function() {
  var oStringList = this.split('-');
  if(oStringList.length == 1)    
    return oStringList[0];
  var ret = this.indexOf("-") == 0 ? 
    oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0];
  for(var i = 1, len = oStringList.length; i < len; i++){
    var s = oStringList[i];
    ret += s.charAt(0).toUpperCase() + s.substring(1)
  }
  return ret;
};

Element.getStyle = function(element, style) {
  element = $(element);
  var value = element.style[style.camelize()];
  if(!value)
    if(document.defaultView && document.defaultView.getComputedStyle) {
      var css = document.defaultView.getComputedStyle(element, null);
      value = (css!=null) ? css.getPropertyValue(style) : null;
    } else if(element.currentStyle) {
      value = element.currentStyle[style.camelize()];
    }
  
  // If top, left, bottom, or right values have been queried, return "auto" for consistency resaons 
  // if position is "static", as Opera (and others?) returns the pixel values relative to root element 
  // (or positioning context?)
  if (window.opera && (style == "left" || style == "top" || style == "right" || style == "bottom"))
    if (Element.getStyle(element, "position") == "static") value = "auto";
    
  if(value=='auto') value = null;
  return value;
};

// converts rgb() and #xxx to #xxxxxx format,
// returns self (or first argument) if not convertable
Element.makePositioned = function(element) {
  element = $(element);
  var pos = Element.getStyle(element, 'position');
  if(pos =='static' || !pos) {
    element._madePositioned = true;
    element.style.position = "relative";
    // Opera returns the offset relative to the positioning context, when an element is position relative 
    // but top and left have not been defined
    if (window.opera){
      element.style.top = 0;
      element.style.left = 0;
    }  
  }
};
  
Element.undoPositioned = function(element) {
  element = $(element);
  if(typeof element._madePositioned != "undefined"){
    element._madePositioned = undefined;
    element.style.position = "";
    element.style.top = "";
    element.style.left = "";
    element.style.bottom = "";
    element.style.right = "";	  
  }
};
Element.getOpacity = function(element){
  var opacity;
  if (opacity = Element.getStyle(element, "opacity"))
    return parseFloat(opacity);
  if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/))
    if(opacity[1]) return parseFloat(opacity[1]) / 100;
  return 1.0;
};

Element.setOpacity = function(element, value){
  element= $(element);
  var els = element.style;
  if (value == 1){
    els.opacity = '0.999999';
    if(/MSIE/.test(navigator.userAgent))
      els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'');
  } else {
    if(value < 0.00001) value = 0;
    els.opacity = value;
    if(/MSIE/.test(navigator.userAgent))
      els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + 
        "alpha(opacity="+value*100+")";
  }  
};

Element.getInlineOpacity = function(element){
  element= $(element);
  var op;
  op = element.style.opacity;
  if (typeof op != "undefined" && op != "") return op;
  return "";
};

Element.setInlineOpacity = function(element, value){
  element= $(element);
  var els = element.style;
  els.opacity = value;
};

Element.getDimensions = function(element){
  element = $(element);
  // All *Width and *Height properties give 0 on elements with display "none", 
  // so enable the element temporarily
  if (Element.getStyle(element,'display') == "none"){
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    els.visibility = "hidden";
    els.position = "absolute";
    els.display = "";
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = "none";
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};    
  }
  
  return {width: element.offsetWidth, height: element.offsetHeight};
};


Position.positionedOffset = function(element) {
  var valueT = 0, valueL = 0;
  do {
    valueT += element.offsetTop  || 0;
    valueL += element.offsetLeft || 0;
    element = element.offsetParent;
    if (element) {
      p = Element.getStyle(element,'position');
      if(p == 'relative' || p == 'absolute') break;
    }
  } while (element);
  return [valueL, valueT];
};

// Safari returns margins on body which is incorrect if the child is absolutely positioned.
// for performance reasons, we create a specialized version of Position.cumulativeOffset for
// KHTML/WebKit only

if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  Position.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      
      if (element.offsetParent==document.body) 
        if (Element.getStyle(element,'position')=='absolute') break;
        
      element = element.offsetParent;
    } while (element);
    return [valueL, valueT];
  };
}

Position.page = function(forElement) {
  var valueT = 0, valueL = 0;

  var element = forElement;
  do {
    valueT += element.offsetTop  || 0;
    valueL += element.offsetLeft || 0;

    // Safari fix
    if (element.offsetParent==document.body)
      if (Element.getStyle(element,'position')=='absolute') break;
      
  } while (element = element.offsetParent);

  element = forElement;
  do {
    valueT -= element.scrollTop  || 0;
    valueL -= element.scrollLeft || 0;    
  } while (element = element.parentNode);

  return [valueL, valueT];
};

// elements with display:none don't return an offsetParent, 
// fall back to  manual calculation
Position.offsetParent = function(element) {
  if(element.offsetParent) return element.offsetParent;
  if(element == document.body) return element;
  
  while ((element = element.parentNode) && element != document.body)
    if (Element.getStyle(element,'position')!='static')
      return element;
  
  return document.body;
};

Position.clone = function(source, target) {
  var options = Object.extend({
    setLeft:    true,
    setTop:     true,
    setWidth:   true,
    setHeight:  true,
    offsetTop:  0,
    offsetLeft: 0
  }, arguments[2] || {});
  
  // find page position of source
  source = $(source);
  var p = Position.page(source);

  // find coordinate system to use
  target = $(target);
  var delta = [0, 0];
  var parent = null;
  // delta [0,0] will do fine with position: fixed elements, 
  // position:absolute needs offsetParent deltas
  if (Element.getStyle(target,'position') == 'absolute') {
    parent = Position.offsetParent(target);
    delta = Position.page(parent);
  }
  
  // correct by body offsets (fixes Safari)
  if (parent==document.body) {
    delta[0] -= document.body.offsetLeft;
    delta[1] -= document.body.offsetTop; 
  }

  // set position
  if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + "px";
  if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + "px";
  if(options.setWidth)  target.style.width = source.offsetWidth + "px";
  if(options.setHeight) target.style.height = source.offsetHeight + "px";
};

Position.absolutize = function(element) {
  element = $(element);
  if(element.style.position=='absolute') return;
  Position.prepare();

  var offsets = Position.positionedOffset(element);
  var top     = offsets[1];
  var left    = offsets[0];
  var width   = element.clientWidth;
  var height  = element.clientHeight;

  element._originalLeft   = left - parseFloat(element.style.left  || 0);
  element._originalTop    = top  - parseFloat(element.style.top || 0);
  element._originalWidth  = element.style.width;
  element._originalHeight = element.style.height;

  element.style.position = 'absolute';
  element.style.top    = top + 'px';
  element.style.left   = left + 'px';
  element.style.width  = width + 'px';
  element.style.height = height + 'px';
};

Position.relativize = function(element) {
  element = $(element);
  if(element.style.position=='relative') return;
  Position.prepare();

  element.style.position = 'relative';
  var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
  var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

  element.style.top    = top + 'px';
  element.style.left   = left + 'px';
  element.style.height = element._originalHeight;
  element.style.width  = element._originalWidth;
};


Element.Class = {
    // Element.toggleClass(element, className) toggles the class being on/off
    // Element.toggleClass(element, className1, className2) toggles between both classes,
    //   defaulting to className1 if neither exist
    toggle: function(element, className) {
      if(Element.Class.has(element, className)) {
        Element.Class.remove(element, className);
        if(arguments.length == 3) Element.Class.add(element, arguments[2]);
      } else {
        Element.Class.add(element, className);
        if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
      }
    },

    // gets space-delimited classnames of an element as an array
    get: function(element) {
      return $(element).className.split(' ');
    },

    // functions adapted from original functions by Gavin Kistner
    remove: function(element) {
      element = $(element);
      var removeClasses = arguments;
      $R(1,arguments.length-1).each( function(index) {
        element.className = 
          element.className.split(' ').reject( 
            function(klass) { return (klass == removeClasses[index]) } ).join(' ');
      });
    },

    add: function(element) {
      element = $(element);
      for(var i = 1; i < arguments.length; i++) {
        Element.Class.remove(element, arguments[i]);
        element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
      }
    },

    // returns true if all given classes exist in said element
    has: function(element) {
      element = $(element);
      if(!element || !element.className) return false;
      var regEx;
      for(var i = 1; i < arguments.length; i++) {
        if((typeof arguments[i] == 'object') && 
          (arguments[i].constructor == Array)) {
          for(var j = 0; j < arguments[i].length; j++) {
            regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
            if(!regEx.test(element.className)) return false;
          }
        } else {
          regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
          if(!regEx.test(element.className)) return false;
        }
      }
      return true;
    }
};
/******************************************************************
Example Layer definitions
the value from DEFAULTS (actually _D in wmsmap.js) is used if that
parameter is not specified in a give layer definition

******************************************************************/
var DEFAULTS = {
      URL: 'http://wms.jpl.nasa.gov/wms.cgi?', // url of WMS server
      VERSION: '1.1.1', // 1.1.1 is default, not needed 
      REQUEST: 'GetMap', // GetMap is default, not needed
      LAYERS: ['global_mosaic'],  // an array of the layers to get
      FORMAT: 'image/png', // this is often just 'png' or 'jpg' 
      SRS: 'EPSG:4326',    // Projection: Lat/Lon (4326) is the default
      STYLES: [],     // A list of styles, see layer def  
      BBOX: [-180,-90,180,90], // [xmin,ymin,xmax,ymax]
      TRANSPARENT: 'TRUE',  
      EXCEPTIONS: 'application/vnd.ogc.se_inimage'
      //on error, return empty image
};

var JPL = {
      URL: 'http://wms.jpl.nasa.gov/wms.cgi?',
      LAYERS: ['daily_terra'],  
      FORMAT: 'image/jpeg',  
      SRS: 'EPSG:4326',   
      STYLES: [],        
      BBOX: [-180,-90,180,90]
};
var FIRE = {
  URL: 'http://activefiremaps.fs.fed.us/wms/wms.asp?',
  LAYERS: ['MODISDetections'],
  BBOX: [-160,30,-70,65]
};

var VMAP = {
  URL: 'http://geocoder.us/surfer/wms.cgi?',
  LAYERS: ['vmap0'],
  BBOX: [-160,30,-70,65],
  FORMAT: 'image/jpeg'
};

var CUBEWERX = {
  URL: 'http://ceoware2.ccrs.nrcan.gc.ca/cubewerx/cubeserv/cubeserv.cgi?',
  LAYERS: ['CLI_LANDUSE:CEOWARE2','ROADS75M:CEOWARE2'],
  BBOX: [-141,41,-52,68]
};

var BMNG = {
   LAYERS: ['BMNG'],
   FORMAT: 'image/png'
};

var TERRASERVER = {
     CAPABILITIES: 'http://terraservice.net/ogccapabilities.ashx?version=1.1.1&request=GetCapabilties',
     URL: 'http://terraservice.net/ogcmap.ashx?',
     LAYERS : ['DRG'], // also 'DOG' and 'UrbanArea'
     FORMAT: 'jpeg',
     BBOX: [-125,32,-115,42],
     STYLES: ['GeoGrid_Magenta']
    
};
var BERKELEY = {
     URL: 'http://terraservice.net/ogcmap.ashx?',
     LAYERS : ['UrbanArea'], // also 'DOG' and 'UrbanArea'
     FORMAT: 'jpeg',
     BBOX: [-122.2642,37.8673312,-122.254831,37.876706]
    
};


var NASA_GLOBE = {
   URL : 'http://globe.digitalearth.gov/viz-bin/wmt.cgi?',
   FORMAT: 'image/png',
   BBOX: [-180,-90,180,90],
   //there are MANY other layers 
   LAYERS: ['Coastlines', 'RAILS','RIVERS','National','WMT_GRATICULE']
   // 'RTOPO' ,'ATMIN'
};


var MSWMS = {
  CAPABILITIES: 'http://www2.dmsolutions.ca/cgi-bin/mswms_gmap?VERSION=1.1.0&REQUEST=GetCapabilities&SERVICE=wms',
  URL: 'http://www2.dmsolutions.ca/cgi-bin/mswms_gmap?',
  LAYERS:
  ['bathymetry','land_fn','park','drain_fn','drainage','prov_bound','fedlimit','rail','road'],
  // also 'grid'
  FORMAT: 'image/png',
  BBOX: [-153,42,-40,73]
};

// Feature server WFS needs polyline.js
// Not yet Implemented
var MSWFS = {
   CAPABILITIES: 'http://www2.dmsolutions.ca/cgi-bin/mswfs_gmap?version=1.0.0&request=getcapabilities&service=wfs',
   URL: 'http://www2.dmsolutions.ca/cgi-bin/mswfs_gmap?',
   VERSION: '1.0.0',
   REQUEST: 'GetFeature',
   SRS: 'EPSG:42304',
   LAYERS: ['park'],
   BBOX: [-173,42,-14,83]
};
/**************************************************************
*  The list of parameters that are used To create a layer
*  If something is not set, the value in _D is used
*  See example_layers.js for predefined layers 
*****************************************************************/
var _D = {
      URL: 'http://wms.jpl.nasa.gov/wms.cgi?', // url of WMS server
      VERSION: '1.1.1', // 1.1.1 is default, not needed 
      REQUEST: 'GetMap', // GetMap is default, not needed
      LAYERS: ['global_mosaic'],  // an array of the layers to get
      FORMAT: 'image/png', // this is often just 'png' or 'jpg' 
      SRS: 'EPSG:4326',    // 4326 is the default
      STYLES: [],     // A list of styles, see layer def  
      BBOX: [-180,-90,180,90], // [xmin,ymin,xmax,ymax]
      TRANSPARENT: 'TRUE',  
      EXCEPTIONS: 'application/vnd.ogc.se_inimage'
      //on error, return empty image
};

//DEBUG Turn on Debugging
var DEBUG = 0;    
//dbg The html element used in debugging;
var dbg;
var dbg2;
var dbgmouse;
//WMap:: the Class used for all Map stuff (holds WLayer instances) 
var WMap = Class.create();

//:normalizeDegress class method (TODO: fix);
WMap.normalizeDegrees =function(arr){
  // todo: this max should be a parameter
  return arr;
  var max = [360,180];
  if(arr[0]<-max[0]){arr[0]+=max[0];arr[2]+=max[0]}
  if(arr[2]>max[0]){arr[2]-=max[0];arr[0]-=max[0]}
  if(arr[1]<-max[1]){arr[1]+=max[1];arr[3]+=max[1]}
  if(arr[3]>max[1]){arr[3]-=max[1];arr[1]-=max[1]}
  return arr;
}; 

WMap.toggleDEBUG = function(){
  DEBUG=!DEBUG;
  dbg.innerHTML='';dbg2.innerHTML='';dbgmouse.innerHTML="";
};



/**********************************************************
All methods available to a WMap object create a WMap 
object via   oMap = new WMap('htmlDiv',[oLayer]);
where oLayer is defined as in example_layers.js
and htmlDiv must be positioned relatively (in stylesheet)
**********************************************************/
WMap.prototype = {
   
  initialize: function(div_id,wms_arr){
/************* WMap parameters   *******************/
// div_id: the id of the div to hold the map 
// wms_arr: an array of wms_specs (see example_layers.js) 
// e.g.  var wmap = new WMap('map_div',[JPL]);
/***************  End WMap ***************************/
      dbg2 = document.createElement('DIV'); 
      document.forms[0].appendChild(dbg2);
      dbgmouse = document.createElement('DIV'); 
      document.forms[0].appendChild(dbgmouse);
      dbg = document.createElement('DIV'); 
      document.forms[0].appendChild(dbg);
    this.div = $(div_id);
    with(this.div.style){
      overflow = 'hidden';
      position = 'relative';
    }
    // The number of layers of tiles surrounding the center tile.
		// To allow map dragging;
    this.tileBuffer = 1; 
    this.LO = 1; // load other images automatically after first?
    this.nT = this.tileBuffer*2+1;
    this.imagePane = []; // div to hold the 2 sets of images
		// an array to hold images TODO: this will break if this.tileBuffer != 1;
    this.img=[[],[],[]];  
    this.originalMapExtents = wms_arr[0].BBOX;
    this.w = parseInt(this.div.style.width) ;
    this.h = parseInt(this.div.style.height) ;
    var tmp = Position.cumulativeOffset(this.div);
    // this.l and t are the offsets of the 'map' div;
    this.l = tmp[0];
    this.t = tmp[1];
    this.layers = []; 

    /******** B:the div that holds stuff **********/
    this.B =  document.createElement('DIV');
    this.B.id = 'B';
    with(this.B.style){
      zIndex = 0;
      // set height and width to NTILES * the div w , h
      width = this.nT * this.w + 'px';
      height = this.nT * this.h + 'px';
      cursor = 'move';
      position = 'relative';
      left = -this.w*this.tileBuffer + 'px'; 
      top = -this.h*this.tileBuffer  + 'px'; 
      border = '1px solid red';
    }
    this.div.appendChild(this.B);
    /*****************  End B ******************/

    /********** contentPane holds useer added overlays *********/
    this.contentPane = document.createElement('DIV');
    with(this.contentPane.style){
      position = 'absolute';
      zIndex = 30;
      left =  '0px';
      top =  '0px';
      border = '1px solid black';
      width = this.nT * this.w +'px';
      height = this.nT * this.h + 'px';
    }
    this.B.appendChild(this.contentPane);

    /******* RegisterEvents **************/
    this.eventMouseUp  = this.mapMouseUp.bindAsEventListener(this);
    this.eventMouseDown  = this.mapMouseDown.bindAsEventListener(this);
    this.eventMouseMove  = this.mapMouseMove.bindAsEventListener(this);
    this.eventMouseOut = this.mapMouseOut.bindAsEventListener(this);
    this.eventImageLoaded = this.loadOtherImages.bindAsEventListener(this);
    this.eventLastImageLoaded = this.unstretchTiles.bindAsEventListener(this);
    this.mapQuery = this.mapQuery.bindAsEventListener(this);
    this.mapQueryCallback = this.mapQueryCallback.bindAsEventListener(this);
    /****************** End RegisterEvents **************/


    /********* Add Layers to Map and render *************/
    for(var i=0;i<wms_arr.length;i++){
       this.addLayer(wms_arr[i]);
    }; this.mapExtents = this.layers[0].BBOX;
    this.render();
    /********* End Layers and Map ********************/

    // call the functions to run after map is rendered;
    this.eventMapInitialized();  
    this.dragger = new
		Draggable('B',{starteffect:function(){},endeffect:function(){}});
  },

	// Add a layer to the map
  addLayer: function(layer){
    this.layers.push(new WLayer(layer,this));
  },

	// prototype Event stuff once map is drawn
  eventMapInitialized: function(){
    Event.observe('B',"click",this.mapQuery);
    Event.observe('B',"mouseup",this.eventMouseUp);
    Event.observe('B',"mousedown",this.eventMouseDown);
    Event.observe('B',"mousemove",this.eventMouseMove);
    Event.observe(this.div.id,"mouseout",this.eventMouseOut);
  },
  doQuery : 0,
  mapQuery : function(e){
      if(!this.doQuery){return false;}
      var pxy = [Event.pointerX(e)-this.l,Event.pointerY(e)-this.t];
      var gxy = this.pixToGeo(pxy[0],pxy[1]);
      var ul = this.pixToGeo(0,0);
      var br = this.pixToGeo(this.w,this.h);
      var qbbox = [ul[0],br[1],br[0],ul[1]];
      var src = this.layers[0].formURL(qbbox);
      var qlayers = src.match(/layers=.+?(?=&)/i);
      src += '&QUERY_' + qlayers;
      src += '&info_format=text/plain'
      src += '&x=' + pxy[0] + '&y=' + pxy[1]
      src = src.replace(/request=.+?(?=&)/i,'request=GetFeatureInfo');
      if(DEBUG){
          dbg2.innerHTML = '<a href=' + src + '>query</a>';
      }
      src = 'remote_get.php?fn=' + encodeURIComponent(src);
      jfetch(src,this.mapQueryCallback);
  },
  
  mapQueryCallback: function(txt){
      // alert(txt)
  },
  mapMouseOut: function(e){
    this.mouseDown = 0;
    var co = this.currentOffset();
		// if there is decent offset and the mouse is outside
		// the div, its a good time to redraw();
    if(Math.abs(co[0])>this.w/2.2 ||
    Math.abs(co[1])> this.h/2.2){
      //TODO: recenter tiles by reshuffling, not redrawing;
      this.redraw();
    }
    this.dragger.finishDrag(e,false);
  },


  mapMouseMove : function(e){
    this.mapMouseX = Event.pointerX(e)-this.l;
    this.mapMouseY = Event.pointerY(e)-this.t;
    if(DEBUG){
      var gxy = this.pixToGeo(this.mapMouseX,this.mapMouseY);
      var pxy = this.geoToPix(gxy[0],gxy[1]);
      gxy[0]=gxy[0].toString().substr(0,10);
      gxy[1]=gxy[1].toString().substr(0,8);
      dbgmouse.innerHTML ='('+ gxy.toString() +') => (' +pxy.toString()+')'
    }
  },

  mapMouseDown : function(e){
     this.mouseDown = 1;
     this.firstX = Event.pointerX(e) - this.l;
     this.firstY = Event.pointerY(e) - this.t;
  },

  mapMouseUp : function(e){
     this.lastX = Event.pointerX(e) - this.l;
     this.lastY = Event.pointerY(e) - this.t;
     this.mouseDown = 0;
     if(Math.abs(this.lastX-this.firstX) > 1 || Math.abs(this.lastY-this.firstY) > 1){
        this.mapMoved();
        if(DEBUG){
          dbg.innerHTML +='<br/>('+ this.firstX + "," + this.firstY +")"
          + " : (" + this.lastX +','+ this.lastY + ')<br/>';
        }
     }


  },
	// See how far div 'B' is from starting pos
  currentOffset: function(){
     var l0 = -this.w*this.tileBuffer;
     var t0 = -this.h*this.tileBuffer;
     var lc = parseInt(this.B.style.left);
     var tc = parseInt(this.B.style.top);
     return([l0-lc,t0-tc]);
  },

  mapMoved: function(){
    if(DEBUG){
      dbg.innerHTML ='';
      this.layers[0].getCapabilities();
    }
    var p0 = this.pixToGeo(this.firstX , this.firstY);
    var p1 = this.pixToGeo(this.lastX , this.lastY);
    var dX = p0[0]-p1[0]; 
    var dY = p0[1]-p1[1]; 
    if(DEBUG){
      dbg2.innerHTML = 'current offset: ' + this.currentOffset();
    }
    // correct the bounding box after drag
    this.layers.each(function(lyr){
      lyr.BBOX[0]+=dX; lyr.BBOX[2]+=dX;
      lyr.BBOX[1]+=dY; lyr.BBOX[3]+=dY;
      lyr.BBOX = WMap.normalizeDegrees(lyr.BBOX);
    });
    this.mapExtents = this.layers[0].BBOX;
    if(DEBUG){
      dbg.innerHTML +='<br/>New BBOX: '+this.layers[0].BBOX + '<br/>';
      dbg.innerHTML +='new Center: ' + this.getCenterGeo() + '<br/>';
    }
    var co = this.currentOffset();
    if(
    Math.abs(co[0]) > this.w/1.5 ||
    Math.abs(co[1]) > this.h/1.5){
      //TODO: instead of redraw() need to shuffle tiles;
      this.redraw();
    }
    
  },
	// TODO: this would allow reuse of tiles, currently unused because
	// i got confused;
  shuffle: function(xDir,yDir){
    // TODO: think about this:
    if(xDir==-1 || yDir==-1){
    for(var k = 0;k<this.layers.length;k++){
      for(var i=0;i<this.nT+xDir;i++){
        for(var j=0;j<this.nT+yDir;j++){
          $(k+'_'+i+'_'+j).src = $(k+'_'+(i-xDir)+'_'+(j-yDir)).src;
        }
      }
    }
		// can combine this into above
    }else if(xDir || yDir){
    for(var k=this.layers.length-1;k>=0;k--){
      var i = this.nT-xDir;
      while(i-->0){
        var j = this.nT-yDir;
        while(j-->0){
          // TODO: this +/- before xDir/yDir might be whack
          $(k+'_'+i+'_'+j).src = $(k+'_'+(i+xDir)+'_'+(j+yDir)).src;
        }
      }
    }
    }
  },
  // TODO: redraw() is alias of render, not needed;
  redraw: function(){
   this.render();
  },

	// Recenter 'B' div after render()
  recenter: function(){
    this.B.style.left = -this.w + 'px';
    this.B.style.top = -this.h  + 'px';
  },
  serialize: function(i){ return this.layers[i].formURL()},

  render: function(){
    for(var lyr =0;lyr<this.layers.length;lyr++){
      if(!this.imagePane[lyr]){
        this.initImagePane(lyr);
      }
      // other layers are drawn when this image loads
      this.img[lyr][1][1].src = this.layers[lyr].formURL();
      if(DEBUG){
        dbg.innerHTML += "<a href="+this.img[lyr][1][1].src+">"+
      this.img[lyr][1][1].src +"</a>";
      }
    }
  },


  // Initialize the div's and images for map layers
  initImagePane: function(lyr){
    this.curLayer =lyr;
    this.imagePane[lyr] = document.createElement('DIV');
    with(this.imagePane[lyr].style){
      position = 'absolute';
      zIndex =  (lyr+2);
      left =  '0px';
      top =  '0px';;
      width = this.nT * this.w +'px';
      height = this.nT * this.h + 'px';
    }
    //this.img holds the (currently) 9 images 
    // the center one is set here, others below
    this.img[lyr]=[[],[],[]]; 
    var T= this.tileBuffer;
    this.B.appendChild(this.imagePane[lyr]);
    for(var i=-T;i<=T;i++){
      for(var j=-T;j<=T;j++){

         this.img[lyr][i+T][j+T] = document.createElement('img');
         var t=((j+1)*this.h)+'px';
         var l=((i+1)*this.w)+'px';
         with(this.img[lyr][i+T][j+T]){
           style.position = 'absolute';
           style.left= l;
           style.top = t; 
           style.width = this.w + 'px';
           style.height = this.h + 'px';
           id =lyr+'_'+ i+'_'+j;
           src='tpix.gif';
         }
/*           this.img[lyr][i+T][j+T].onload = function(){ this.style.visibility = '' };
*/
           this.img[lyr][i+T][j+T].ol = l; // orignial left, top
           this.img[lyr][i+T][j+T].ot = t; // for unstretching;
         this.imagePane[lyr].appendChild(this.img[lyr][i+T][j+T]);
         if(DEBUG){
           this.img[lyr][i+T][j+T].style.border = '1px solid red';
         }
      }
    }
    // when the first image loads, call the other images;
    Event.observe(this.img[lyr][1][1], "load", this.eventImageLoaded );
//    Event.observe(this.img[lyr][2][2], "load", this.eventLastImageLoaded);
         if(DEBUG){
           this.img[lyr][1][1].style.border = '1px solid blue';
           this.img[lyr][1][1].style.zIndex = 20;
         }
 },

  // return the x, y of the center of the current window;
  getCenterGeo : function (){
    return [(this.mapExtents[0]+this.mapExtents[2])/2,
            (this.mapExtents[1]+this.mapExtents[3])/2,
           ];
  },

	// when the center tile is drawn, load other images
  loadOtherImages : function(){
      // the first image is loaded so we center on that
      if(!this.LO){return false;}
      // then loop through other layers
      for(var lyr=0;lyr<this.layers.length;lyr++){
      var curLayer = this.img[lyr];
      // il : image location in array surrounding center tile;
      var il = this.layers[lyr].BBOX;
      // T : the number of rows aroudn the center (1 => 9 tiles)
      var T = this.tileBuffer;
      var x_range = this.mapExtents[2]-this.mapExtents[0];
      var y_range = this.mapExtents[3]-this.mapExtents[1];
      for(var m=-T;m<=T;m++){
        for(var n=-T;n<=T;n++){
            if((!m) && (!n)){continue;} // already filled 0,0 above
//              curLayer[m+T][n+T] = this.createImg(m,n);
              curLayer[m+T][n+T].BBOX = [
                il[0]+m*x_range, 
                il[1]-n*y_range,
                il[2]+m*x_range,
                il[3]-n*y_range
              ];
              curLayer[m+T][n+T].src = 'tpix.gif';
              /*
              curLayer[m+T][n+T].style.visibility = 'none';
              */
              curLayer[m+T][n+T].src=this.layers[lyr].formURL(curLayer[m+T][n+T].BBOX);
           }
        }
      }
      this.unstretchTiles();
      this.recenter();
      this.redrawOverlays();
    },
	 // convert geographic coordinates to pixel coordinates.
	 // gX - int, the x coordinate in geo units
	 // gY - int, the y coordinate in geo units
    geoToPix : function( gX, gY ) {
        var gx_range = this.mapExtents[2]-this.mapExtents[0];
        var gy_range = this.mapExtents[3]-this.mapExtents[1];
        var pX = (gX-this.mapExtents[0])/gx_range * this.w ;
        var pY = (this.mapExtents[3]-gY)/gy_range * this.h ;
        return [Math.round(pX), Math.round(pY)];
    },

  // convert pixel coordinates to geographic coordinates.
  // pX - int, the x coordinate in pixel units
  // pY - int, the y coordinate in pixel units
    pixToGeo : function( pX, pY ) {
    // TODO: make gx/y_range a property since used often
      var gx_range = this.mapExtents[2]-this.mapExtents[0];
      var gy_range = this.mapExtents[3]-this.mapExtents[1];
      var gX = this.mapExtents[0] + (pX/this.w)*gx_range;
      var gY = this.mapExtents[1] + (1-pY/this.h) * gy_range;
      return [gX, gY];
    },
		// center on gX,gY
    centerOnPointGeo: function(gX,gY){
      var gx_rad = (this.mapExtents[2]-this.mapExtents[0])/2;
      var gy_rad = (this.mapExtents[3]-this.mapExtents[1])/2;
      this.mapExtents[0]=gX-gx_rad; this.mapExtents[1]=gY-gy_rad;
      this.mapExtents[2]=gX+gx_rad; this.mapExtents[3]=gY+gy_rad;
      this.layers.each(function(lyr){
        lyr.requestString = lyr.formURL();
      });
      this.render();
    },
    zoomIn : function(zF){
      zF=(zF)?2*zF:4;
      this.zoom(zF);
    },
		// called by zoomIn, zoomOut
    zoom : function(zF){
      this.stretchTiles(zF);
      // zF is zoomFactor, use negative to zoom out
      var dX = (this.mapExtents[2]-this.mapExtents[0])/zF;
      var dY = (this.mapExtents[3]-this.mapExtents[1])/zF;
      this.mapExtents[0]+=dX; this.mapExtents[1]+=dY;
      this.mapExtents[2]-=dX; this.mapExtents[3]-=dY;
      this.layers.each(function(lyr){
        lyr.requestString = lyr.formURL();
      });
      this.render();
      if(DEBUG){
        dbg2.innerHTML = '<br/> center x, y: ' + this.getCenterGeo().toString();
      }
    },
    stretchTiles : function(zF){
    // TODO: this only works if the currentOffsetX,Y are 0.
      return 1;
      var dX, dY;
      zF=(zF<0)?zF:zF;
      var co = this.currentOffset();
      with(this.B.style){
        dX = parseInt(width)/zF;
        dY = parseInt(height)/zF;
        alert(dX + " | " + dY);
        var mf = (Math.abs(zF)!=zF);
        width = dX/2 + parseInt(width) +'px';
        height = dY/2 + parseInt(height) + 'px';
        left = co[0]-dX + parseInt(left) + 'px';
        top = co[1]-dY + parseInt(top) + 'px';
      }
      var T = this.tileBuffer;
      for(var lyr=0;lyr<this.layers.length;lyr++){
         dX = this.w/zF;
         dY = this.h/zF;
         var newImgWidth = this.w +2* dX;
         var newImgHeight = this.h +2* dY;
         for(var i=0;i<(2*T)+1;i++){
           for(var j=0;j<(2*T)+1;j++){
             with(this.img[lyr][i][j].style){
               width=newImgWidth + 'px';
               height=newImgHeight + 'px';
               left = parseInt(left) +2*dX;
               top = parseInt(top)+2*dY;
             }
          }
        }
      }
    },
		// TODO: fix this, with stretch, speeds zoomIn,Out
    unstretchTiles: function(zF){
      return 1;
      var T = this.tileBuffer;
      with(this.B.style){
        width = this.w*this.nT + 'px'; 
        height =this.h*this.nT+'px';
        // this below gets done by recenter:
//        left =-this.w*T+  'px';
 //       top = -this.h*T+'px';
      }
      for(var lyr=0;lyr<this.layers.length;lyr++){
         for(var i=0;i<(2*T)+1;i++){
           for(var j=0;j<(2*T)+1;j++){
               with(this.img[lyr][i][j].style){
                 // use 'with'
                 width=this.w + 'px';
                 height=this.h + 'px';
                 left=this.img[lyr][i][j].ol;
                 top=this.img[lyr][i][j].ot;
               }
          }
        }
      }


    },

    zoomOut : function(zF){
      // this number 4 controls the zoom factors
      zF=(zF)?zF:2;
      this.zoom(-zF);
    }

}; // End Wmap



/****************************************************************
WLayer:: create a layer definition as a javascript object, seee
example_layers.js.  These are added to the map.
can also be used to getCapabilities or serialize (get URL);

****************************************************************/

var WLayer = Class.create();
WLayer.prototype = {
  initialize: function(spec,omap){
    // TODO:this wll jnot work for later layers
    // may hvae broken things here
    for(var k in _D){
      this[k] = spec[k] || _D[k];
    }
    for(var k in spec){
      this[k] = spec[k];
    }

    if(typeof(this.BBOX)=='string'){
      this.BBOX = this.BBOX.split(/,/);
    }
    this.initialExtents = this.BBOX;
    this.parent_map = omap;
    this.requestString = this.formURL();
  },
  formURL: function(BBOX){
    // this is horrible use a class method 
    if(BBOX){
      var tmp = BBOX;
      BBOX = this.BBOX;
      this.BBOX=tmp;
    }
    var url = this.URL;
    for (var par in _D){
      if(par == 'URL'){ continue;}
      var val = this[par] || "";
      url += '&' + par + '=' + val.toString();
    }
    url +='&width=' +this.parent_map.w;
    url +='&height=' +this.parent_map.h;
    if(BBOX){ this.BBOX=BBOX; }
    return url;
  },
  getCapabilities: function(){
    var c = this.CAPABILITIES || this.URL;
    c+= '&VERSION=' +
    this.VERSION + '&REQUEST=GetCapabilities&SERVICE=';
      c +=  this.SERVICE || 'wms';
    dbg.innerHTML += '<a href='+c+'>Get Capabilities</a>';
  }
  
};
/********************************************************************
add a googlemaps style marker to the map

map = new WMap('map',[TERRASERVER]);
var marker = map.addOverlayGeo(-120, 34,'http://www.google.com/mapfiles/marker.png');

// add an onmouseover event
    marker.onmouseover = function(){document.getElementById('extra').innerHTML =
    '<font color=blue> any image can be used as a marker </font>'};
// add an onclick event
    marker.onclick = function(){document.getElementById('extra').innerHTML =
    '<font color=blue> any image can be used as a marker </font>'};


********************************************************************/
WMap.prototype.addOverlay = function(gX,gY,imgsrc){
  var p = this.geoToPix(gX,gY);
  p.push(gX);p.push(gY);
  return this.addOverlayPix(p,imgsrc,arguments[3] || '');
};
// remove an overlay
WMap.prototype.removeOverlay = function(o){
  var c = this.contentPane.childNodes;
  for(var i=0;i<c.length;i++){
    if(o==c[i]){
      this.contentPane.removeChild(c[i]);
    }
  }
};

// remove all overlays
WMap.prototype.removeOverlays = function(){
  var c = this.contentPane.childNodes;
  var ret=[];
  for(var i=0;i<c.length;i++){
    ret = c[i];
    this.contentPane.removeChild(c[i]);
  }
};
// INTERNAL
WMap.prototype.addOverlayPix = function(pt,imgsrc){
    var offset = arguments[3] || 0;
    var o = document.createElement('img');
		o.src = imgsrc;
    o.isrc = imgsrc;
    o.gX = pt[2];
    o.gY = pt[3];
    var w=this.w;var h=this.h;
    with(o.style){
      position='absolute';
      cursor = 'default';
    }
    this.contentPane.appendChild(o);
		return o;
};

// INTERNAL: used after zoom
WMap.prototype.redrawOverlays = function(){
//  var ovls = this.removeOverlays();
  var ovls = this.contentPane.childNodes;
  for(var i=0;i<ovls.length;i++){
    var o = ovls[i];
    var p = this.geoToPix(o.gX,o.gY);
    var l = -o.width/2+this.w+p[0];
    var t = -o.height+this.h+p[1];
    o.style.left = l + 'px';
    o.style.top = t + 'px';
   }
};

// this doen'st really belong here, but its short and simple.
// see: http://128.32.253.220/jslibs/jfetch.js
function jfetch(url,target,context) {
  var req = (typeof XMLHttpRequest!= undefined )?new XMLHttpRequest():new ActiveXObject("Microsoft.XMLHTTP");
  req.open("GET",url,true);
  req.onreadystatechange = function() {
    if(req.readyState == 4){
      var rsp = req.responseText;
      if(target.constructor == Function){
        target.call(context,rsp);
      }else{
        var t = document.getElementById(target);
        if(t.value==undefined){t.innerHTML=rsp;}else{t.value=rsp;}
      }
    }
  };
  req.send(null);
}
