﻿/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.

  BpMap - A GMap2 subclass
    getOverlays(...)
    zoomTo(...)
    dis/enableZooming/zoomingDisabled // requires checking zoomingDisabled ("Enabled"?) in zoomend handlers
    mapArray(...)
    map.getInfoWindowOpener()
    map.setOnEndListeners(...)
    BpLabel
    BpTooltip
    BP_SMALL_ICON, BP_APPROX_ICON, ... //  needs testing
    addLayer // soon to be supported

  GOverlay:
    getId // only good after addOverlay
    get/setUserData

  GMarker:
    getRecycled // reuse removed markers if available
    getMap
    isMapped
    setTooltip
    un/setHoverImage
    get/setOpacity // not supported for MSIE, and doesn't conform to API so may go away (gracefully) one day
    get/set/reset/setHighZIndex

  BpLabel:
    ...

todo:
  use this for all future projects
  addLayer
  completely rework GMarker.getRecycled
  test/check usability of BpIcon

*/


(function(){
function BpMap(div, opts) {
  if (typeof div == 'string') {
    div = document.getElementById(div);
  }

  this._bp = {};

  GMap2.call(this, div, opts);

  BP_BROWSER
  setupGetOverlays(this);
//  setupSetTooltip(GMarker);
  setupZoomTo(this);
  setupMapArray(this);
  setupDisableZooming(this);
  setupSetOnEndListeners(this);
  setupGetInfoWindowOpener(this, GMarker);
//  setupGetMap(this);
  setupAddLayer(this);

  this.addBitPerfectLogo();
}

for (var prop in GMap2.prototype)
  BpMap.prototype[prop] = GMap2.prototype[prop];


BpMap.prototype.removeBitPerfectLogo = function() {
  this.removeControl(this._bp._logo);
};

BpMap.prototype.addBitPerfectLogo = function() {
  if (!this._bp._logo) {
    this._bp._logo = new BpLogoControl();
  }
  this.addControl(this._bp._logo);
};

/*
// BpBrowser 0.13 Copyright 2006-2009 BitPerfect http://www.gmaptools.com - All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)


BP_BROWSER.type     == BPBROWSER.MSIE, etc
BP_BROWSER.os       == BPBROWSER.MAC,  etc
BP_BROWSER.version  <= 6

*/

(function(){

var browsers = ['chrome','opera','msie','safari','firefox','mozilla'];
var oses = ['x11;','macintosh','windows'];

function browser(ua){
  this.CHROME = 0;
  this.OPERA = 1;
  this.MSIE = 2;
  this.SAFARI = 3;
  this.FIREFOX = 4;
  this.MOZILLA = 5;
  this.X11 = 0;
  this.MAC = 1;
  this.WINDOWS = 2;
  this.type = -1;
  this.os = -1;
  this.version = 0;
  ua = ua.toLowerCase();
  for (var i = 0; i < browsers.length; i++) {
    if (ua.indexOf(browsers[i]) != -1) {
      this.type = i;
      var version_re = new RegExp(browsers[i] + '[ /]?([0-9]+)');
      if (version_re.exec(ua)) {
        this.version = parseFloat(RegExp.$1);
      }
      break;
    }
  }
  for (var i = 0; i < oses.length; i++) {
    if (ua.indexOf(oses[i]) != -1) {
      this.os = i;
      break
    }
  }
}

window.BP_BROWSER = window.BpBrowser = new browser(navigator.userAgent);

})();
/*
  BpLogo - add a logo link to the bottom corner of the map

todo: test this? otherwise, nothing

*/
(function(){

function BpLogoControl(){
  GControl.apply(this,arguments);
}

BpLogoControl.prototype = new GControl();

BpLogoControl.prototype.initialize = function(map) {
  var img = document.createElement('img');
  img.setAttribute('border', '0');

  var src = 'http://www.gmaptools.com/images/bplogo.png';
  if (BP_BROWSER.type == BP_BROWSER.MSIE && BP_BROWSER.version <= 6) {
    img.setAttribute('src', 'http://www.gmaptools.com/images/clear.gif');
    img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=crop; src=" + src + ")";
  }
  else {
    img.setAttribute('src', src);
  }

  try {
    img.style.cursor = 'pointer';
  }
  catch (e) {
    img.style.cursor = 'hand';
  }

  var a = document.createElement("a");
  a.setAttribute('title', 'Click to visit GMapTools.com');
  a.setAttribute('href', 'http://www.gmaptools.com/maplink');
  a.appendChild(img);

  var div = document.createElement("div");
  div.appendChild(a);
  map.getContainer().appendChild(div);
  return div;
};

BpLogoControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(4, 35));
};

window.BpLogoControl = BpLogoControl;

})();
/*
Copyright 2005-2007 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

  BpControl
  
0.1  - initial version
0.11 - get/setMessage()
			 show/hide()
			 got rid of the divId argument
			 added className argument, get/setClassName
0.12 - renamed the class/file to BpControl
0.13 - changed "message" to "content"
0.14 - subclasses can manage style with setStyle()
       reformatting
       wrapped in anonymous function
       isVisible

*/

(function(){
var bpId = 0;
function BpControl(bpMsg,bpSize,bpCorner,bpClassName) {
    if(!bpMsg)    bpMsg    = 'Loading...';
    if(!bpSize)   bpSize   = new GSize(100,100);
    if(!bpCorner) bpCorner = G_ANCHOR_TOP_LEFT;

    this.bpMsg       = bpMsg;
    this.bpSize      = bpSize;
    this.bpCorner    = bpCorner;
    this.bpClassName = bpClassName;

    this.bpId     = ++bpId;
    this.bpDivId  = 'bpcontrol' + this.bpId;
}

BpControl.prototype = new GControl();

var bpProto = BpControl.prototype;

bpProto.getDivId = function() {
    return this.bpDivId;
};

bpProto.getDiv = function() {
    return this.bpDiv;
};

bpProto.getContent = function() {
    return this.bpDiv.innerHTML;
};

bpProto.setContent = function(bpMsg) {
    this.bpDiv.innerHTML = bpMsg;
};

bpProto.setContentNode = function(bpNode) {
    var bpDiv = this.getDiv();
    while (bpDiv.firstChild)
        bpDiv.removeChild(bpDiv.firstChild);

    bpDiv.appendChild = bpNode;
};

bpProto.show = function() {
    this.bpDiv.style.display = '';
};

bpProto.hide = function() {
    this.bpDiv.style.display = 'none';
};

bpProto.getClassName = function() {
    return this.bpClassName;
};

bpProto.setClassName = function(bpClassName) {
    if(!this.bpClassName)
        return;

    this.bpClassName = bpClassName;
    this.bpDiv.className = bpClassName;
};

bpProto.initialize = function(bpMap) {
    var bpDiv = document.createElement('div');
    bpDiv.setAttribute('id',this.bpId);

    this.bpDiv = bpDiv;

    var bpStyle = bpDiv.style;
    bpStyle.display = 'none';

    if(this.bpClassName) {
        bpDiv.className = this.bpClassName;
    } else if (typeof(this.setStyle) == 'function') {
        this.setStyle();
    } else {
        bpStyle.border = '1px solid black';
        bpStyle.backgroundColor = 'white';
        bpStyle.fontWeight   = 'bold';
        bpStyle.paddingLeft  = '3px';
        bpStyle.paddingRight = '3px';
    }
  
    bpDiv.innerHTML = this.bpMsg;
    bpMap.getContainer().appendChild(bpDiv);
    this._map = bpMap;
    return bpDiv;
};

bpProto.isVisible = function() {
    return this.bpDiv.style.display != 'none';
};

bpProto.getDefaultPosition = function() {
    return new GControlPosition(this.bpCorner,this.bpSize);
};

bpProto.printable  = function() { return false; };
bpProto.selectable = function() { return false; };

window.BpControl = BpControl;
})();
/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

    call setupGetOverlays(map) to add these methods to the map:
        map.getOverlays()
        map.removeOverlays(overlaysToRemove?) -- call this instead of clearOverlays()
        map.addOverlays(overlaysArray)

    overlays which are added to the map and have an _ignoreOverlay property are ignored by these methods
        this can be useful if you have overlays like a map singleton tooltip which you don't ever want to 
            manipulate with the rest of the overlays

0.1  - initial version
0.11 - removeOverlays()
          this calls removeOverlay for each overlay, and also optionally takes an array of
          overlays to remove instead of this.getOverlays()
       addOverlays(overlays)
       don't add the infoWindow to the overlays array
0.12 - getOverlayById(id [, field [, attr] ] ) -- find a mapped marker by "m._id", "m.getUserData()[field]" or "m[attr][field]" 
0.2 -- rewrite with optional arguments
       removeOverlays: no arguments removes all non-ignored overlays
                       an array argument removes those overlays
                       a boolean true argument removes all overlays, including ignored ones
       GOverlay.getId -- this is good for any marker that's been added to the map, unique for that map instance

todo:
  getOverlays and removeOverlays should take the same arguments
    removeOverlays should also take an array of overlays (non-functions) as another alternative
  allow for non-getLatLng overlay classes (GPolygon, GPolyline, etc) able to be searched by inview
    they are returned by default. To limit this, just pass GMarker or [GMarker] (or some such) as the first argument

*/
function setupGetOverlays(map) {

  if(typeof(map.getOverlays) == 'function') {
    return;
  }

  if (typeof map._bp == 'undefined') {
    map._bp = {};
  }
  map._bp._overlays = {};
  map._bp._nextOverlayId = 1;

  GEvent.addListener(map, 'addoverlay', function(overlay) {
    if (!overlay._bp) {
      overlay._bp = {};
    }

    if(overlay === this.getInfoWindow()) {
      return;
    }

    if(!overlay._bp._id) {
      overlay._bp._id = this._bp._nextOverlayId++;
    }

    this._bp._overlays[overlay._bp._id] = overlay;
  });

  GEvent.addListener(map, 'removeoverlay', function(overlay) {
    if(!overlay._bp || !overlay._bp._id) {
      return;
    }

    delete this._bp._overlays[overlay._bp._id];
  });

  GEvent.addListener(map, 'clearoverlays', function() {
    this._bp._overlays = {};
  });

  map.getOverlays = function(types, inview, include_ignore) {
    if (types && !(types instanceof Array)) { // requesting a single type
      types = [types];
    }

    var a = [];
    var overlays = this._bp._overlays;
    var bounds = this.getBounds();
    for (var id in overlays) {
      var overlay = overlays[id];
      var type_ok = !types;
      if (types instanceof Array) {
        for (var i = 0; i < types.length; i++) {
          if (overlay instanceof types[i]) {
            type_ok = true;
            break;
          }
        }
      }
      // inview: not boolean? include everything; true include inview only; false, include off-view only
      // only use this feature with overlays that have a getLatLng method; otherwise, include them if we're checking this
      var inview_ok = !(typeof inview == 'boolean' && typeof overlay.getLatLng == 'function');
      if (!inview_ok) {
        if (inview) { // true: must be in view
          inview_ok = bounds.containsLatLng(overlay.getLatLng());
        }
        else { // false: must be off-view
          inview_ok = !bounds.containsLatLng(overlay.getLatLng());
        }
      }
      if (type_ok && inview_ok && (!overlay._bp._ignore || include_ignore)) {
        a.push(overlay);
      }
    }
    return a;
  };

  map.removeOverlays = function(overlays) {
    if (arguments.length == 0) {
      overlays = this.getOverlays();
    }
    else if (typeof overlays == 'boolean' && overlays) { // include ignored
      overlays = this.getOverlays(false, null, true);
    }

    for (var i = 0; i < overlays.length; i++) {
      this.removeOverlay(overlays[i]);
    }
  };

  map.addOverlays = function(overlays) {
    if (overlays instanceof Array) {
      for (var i = 0; i < overlays.length; i++) {
        this.addOverlay(overlays[i]);
      }
    }
  };

  // good for any overlay that's already been added to the map
  GOverlay.prototype.getId = function() {
    if (!this._bp) {
      this._bp = {};
    }

    if (typeof this._bp._id == 'number') {
      return this._bp._id
    }
    else {
      return false;
    }
  };

  // id is the id given to this overlay, accessed as overlay._bp._id
  // if field is present, then the first overlay passing "overlay.getUserData()[field] == id" is returned
  map.getOverlay = function(id_or_fieldname, fieldvalue) {
    var overlays = this.getOverlays();

    for (var i = 0; i < overlays.length; i++) {
      if (arguments.length >= 2 && overlays[i].getUserData()[id_or_fieldname] == fieldvalue) {
        return overlays[i];
      }
      else if (overlays[i]._bp._id == id_or_fieldname) {
        return overlays[i];
      }
    }
  };

}
setupGetOverlays.version = 0.2;
/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

0.1 -- needs quite a bit more testing for some things

todo:
  
  better tests?
  add support for int'l dateline

*/
function setupZoomTo(map) {
  if(typeof(map.zoomTo) == 'function') {
    return;
  }

  map.zoomTo = function(margin, points, maxZoom) {
    // if there are no points specified, use the mapped markers' points
    if (!points) {
      var m = this.getOverlays([GMarker,GPolyline,GPolygon]);
      if(m.length == 0) {
        return; // return if we don't know where to zoom
      }

      // get the points of the markers
      var points = [];
      for(var i = 0; i < m.length; i++) {
        points.push(m[i].getPoint());
      }
    }
    else if (points.length && (points[0].getPoint || points[0].getLatLng)) {
      // there are markers specified, so get their points
      var p = [];
      for(var i = 0; i < points.length; i++) {
        if (points[i].getPoint) {
          p.push(points[i].getPoint());
        }
        else {
          p.push(points[i].getLatLng());
        }
      }
      points = p;
    }
    else if (points.length && points[0].getVertexCount) {
      
    }

    if(!points[0]) {
      return;
    }

    margin = margin || 0; // set the default (a percentage of the span)

    // create the bounds to zoom to
    var bounds = new GLatLngBounds(points[0]);
    for(var i = 1; i < points.length; i++) {
      bounds.extend(points[i]);
    }

    var zoom = this.getBoundsZoomLevel(bounds);
    if(arguments.length > 2 && zoom > maxZoom) {
      zoom = maxZoom;
    }

    this.setCenter(bounds.getCenter(), zoom);
  };
}
setupZoomTo.version = 0.2;
/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

	map.mapArray(dataArray,{step:1,mkMarker=MyMarkerClass,onstep:function(){},ondone=function(){},progressbar=new ProgressbarControl())

step is 1 by default
mkMarker, called as a method of the map, just uses lat & lng properties of the data
ondone, also called as a method of the map, is called after the last marker has been mapped.
progressbar: and optional progressbarcontrol

This will dynamically map an array of data. The data should be an 
array of objects looking something like this:

{ lat: 42, lng: 42, other: data, here: etc }

total items to be mapped
where we are at this point

0.1  - initial
0.11 - allow for adding the method to another object/class
       is not one shared config variable
       optional args are object propertied, not listed
       call onstep after each step, passing it:
          the total count of mapped markers so far
          the count of all markers
       call ondone after we're done, passing it: the mapped marker count
0.12 - it can handle more than one request at a time; they're executed in sequence;
          only the final opts.ondone is called. The rest are discarded.
       onstep will be called at the end of a queued array, if it's not the last array in the queue
       ondone will be called if it's the last array in the queue
        this assumes that you'll want the same ondone handler for any array you pass,
          and it will want to end mapArray processing

TODO:
  none

*/

function setupMapArray(map) {

  function doMapArray() {
    if (this._mapArrayConfig.queue.length == 0)
      return; // interrupted

    var key = this._mapArrayConfig.queue[0];
    var config = this._mapArrayConfig.todo[key];

    var maxIndex = Math.min(config.startIndex + config.step, config.dataArray.length);

    var tt = typeof(this.getTooltip) == 'function' ? this.getTooltip() : null;
    for(var i = config.startIndex; i < maxIndex; i++) {
      var marker = config.mkMarker.call(this, config.dataArray[i]);
      this.addOverlay(marker);
      if (tt)
        marker.setTooltip(tt, config.dataArray[i].title);
    }

    if(maxIndex < config.dataArray.length) {
      config.startIndex = maxIndex;
      var total_mapped_count = maxIndex + map._mapArrayConfig.totalMappedCount;
      var total_count = map._mapArrayConfig.totalMappedCount + map._mapArrayConfig.totalCount;
      if (config.progressbar) {
        config.progressbar.updateLoader(config.step);
      }
      config.onstep.call(this, total_mapped_count, total_count);
      setTimeout(GEvent.callback(this, doMapArray), 1);
    }
    else {
      delete this._mapArrayConfig.todo[key];
      this._mapArrayConfig.queue.shift(); // remove this key from the queue

      // if there are other requests to process, process them - don't call ondone
      map._mapArrayConfig.totalMappedCount += maxIndex;
      if (this._mapArrayConfig.queue.length > 0) {
        total_mapped_count = map._mapArrayConfig.totalMappedCount;
        total_count = map._mapArrayConfig.totalMappedCount + map._mapArrayConfig.totalCount;
        if (config.progressbar) {
          config.progressbar.updateLoader(config.step);
        }
        config.onstep.call(this, total_mapped_count, total_count);
        setTimeout(GEvent.callback(this, doMapArray), 1);
      }
      // last request? call config.ondone
      else {
        total_count = map._mapArrayConfig.totalMappedCount;
        if (config.progressbar) {
          config.progressbar.updateLoader(config.step);
        }
        config.onstep.call(this, total_count, total_count);
        if (config.progressbar) {
          config.progressbar.remove();
        }
        config.ondone.call(this, total_count);
        // reset everything to the default
        map.stopMapArray();
      }
    }
  }

  function defaultMkMarker(d, tt) {
    var lat     = parseFloat(d.lat);
    var lng     = parseFloat(d.lng);
    var latlng  = new GLatLng(lat, lng)
    var marker  = new GMarker(latlng);
    if (marker.setUserData) marker.setUserData(d);
    return marker;
  }

  map.stopMapArray = function() {
    this._mapArrayConfig = { todo: {}, queue: [], totalMappedCount: 0, completedArrays: [], totalCount: 0 };
  };
  map.stopMapArray(); // also good for initialization

  map.mapArray = function(dataArray, args, key) {
    if (arguments.length < 3)
      key = 'one-and-only';

    if (typeof(this._mapArrayConfig.todo[key]) == 'undefined')
      this._mapArrayConfig.todo[key] = {};
    else // only one request per key at a time
      return false;

    if (arguments.length < 2)
      args = {};

    // store the config
    this._mapArrayConfig.todo[key].dataArray   = dataArray;
    this._mapArrayConfig.todo[key].step        = args.step        ? args.step         : 1;
    this._mapArrayConfig.todo[key].mkMarker    = args.mkMarker    ? args.mkMarker     : defaultMkMarker;
    this._mapArrayConfig.todo[key].onstep      = args.onstep      ? args.onstep       : function(){};
    this._mapArrayConfig.todo[key].ondone      = args.ondone      ? args.ondone       : function(){};
    this._mapArrayConfig.todo[key].progressbar = args.progressbar ? args.progressbar  : false;
    this._mapArrayConfig.todo[key].startIndex  = 0;

    this._mapArrayConfig.queue.push(key);
    this._mapArrayConfig.totalCount += dataArray.length;

    if (this._mapArrayConfig.queue.length == 1) {
      if (args.progressbar) {
        args.progressbar.start(dataArray.length);
      }
      setTimeout(GEvent.callback(this, doMapArray), 1);
    }

    return true;
  };
}
/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

	map.disableZooming()
	map.enableZooming()
	var bool = map.zoomingEnabled()


Here are the ways we can set the zoom:
  using the map control
  
  zoomIn()
  zoomOut()
  setZoom()
  setCenter(center,zoom)

Doing these when zooming is disabled will result in the zoom being set twice - once for the original zoom,
  and once when we re-set the zoom back to where we want it to stay. Zoomend and moveend handlers will
  therefore be called twice each.

So, in zoomand and moveend handlers, do this first:
  if(!map.zoomingEnabled()) return;

That way, your handlers will work only if you want them to. Just be careful, because setCenter() will
  change the center, but not the zoom, if it's called when zooming is disabled.

*/

function setupDisableZooming(map) {

  map.disableZooming = function() {
    this.__disableZoomingZoomLevel = this.getZoom();
    this.__disableZoomingListener = GEvent.addListener(this,'zoomend',function(){
      if(typeof(this.__disableZoomingZoomLevel) != 'undefined')
        this.setZoom(this.__disableZoomingZoomLevel);
    });
  };
  
  map.enableZooming = function() {
    delete this.__disableZoomingZoomLevel;
    if(typeof(this.__disableZoomingListener) == 'object')
      GEvent.removeListener(this.__disableZoomingListener);
    delete this.__disableZoomingListener;
  };
  
  map.zoomingEnabled = function() {
    return typeof(this.__disableZoomingListener) == 'undefined';
  };
  
}/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

0.1  - initial
0.11 - change args around
       make the function a method of the map
0.12 - BpEvent is required, so that handlers do not get called if zooming (and other map events) are disabled

*/
function setupSetOnEndListeners(map) {

  map.setOnEndListeners = function(handlers) {
  	if(typeof(this.__onEndListenersCurrentZoom) != 'undefined')
  		return;
  
    // unpack handlers
    var zoomInHandler  = handlers.zoomIn  || function(){};
    var zoomOutHandler = handlers.zoomOut || function(){};
    var moveEndHandler = handlers.moveEnd || function(){};
  
    // called as a method of the map onzoomend and onmoveend
    // calls appropriate handler/s depending on the type of move/zoom performed
    function onEndHandler(oldZoom,newZoom) {
      if(arguments.length == 0) {
        var thisZoom = this.getZoom();
        if(this.__onEndListenersCurrentZoom != thisZoom)
          this.__onEndListenersCurrentZoom = thisZoom;
        else
          this.__onEndListenersOnMoveEnd();
      } else if(oldZoom < newZoom) {
        this.__onEndListenersOnZoomIn(oldZoom,newZoom);
      } else { // newZoom < oldZoom
        this.__onEndListenersOnZoomOut(oldZoom,newZoom);
      }
    }
  
    // store the current zoom
    this.__onEndListenersCurrentZoom = map.getZoom();
  
    // store the handlers
    this.__onEndListenersOnZoomIn  = zoomInHandler;
    this.__onEndListenersOnZoomOut = zoomOutHandler;
    this.__onEndListenersOnMoveEnd = moveEndHandler;
  
    // store the listeners
  	this.__onEndListenersZoomEndListener = GEvent.addListener(map,'moveend',onEndHandler);
  	this.__onEndListenersZoomEndListener = GEvent.addListener(map,'zoomend',onEndHandler);
  };
  setupSetOnEndListeners.version = 0.12;

  map.removeOnEndListeners = function() {
    // remove the listeners
    GEvent.removeListener(this.__onEndListenersMoveEndListener);
    GEvent.removeListener(this.__onEndListenersZoomEndListener);
  
    // delete the current zoom, the handlers, and the listeners
    delete this.__onEndListenersCurrentZoom;
    delete this.__onEndListenersMoveEndListener;
    delete this.__onEndListenersZoomEndListener;
    delete this.__onEndListenersOnZoomIn;
    delete this.__onEndListenersOnZoomOut;
    delete this.__onEndListenersOnMoveEnd;
  };
  
}
/*
Copyright 2005-2007 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

0.1 - only works for things which already can open infoWindows
        GMarkers and BpMarkers

todo:
  make it work for multiple maps on the same page
    no "global" infoWindowOpener var

*/
(function(){
var makers = {};
var infoWindowOpener;

function setupGetInfoWindowOpener(map, maker) {
  if(typeof(makers[maker]) != 'undefined')
    return;

  if (typeof(map.getInfoWindowOpener) != 'function') {
    map.getInfoWindowOpener = function() {
      return infoWindowOpener;
    };
  
    map.setInfoWindowOpener = function(iwOpener) {
      infoWindowOpener = iwOpener;
    };
  
    GEvent.addListener(map,'infowindowclose',function(){
      infoWindowOpener = null;
    });
  }
  
  function createCallback(origMethod) {
    return function() {
      if(!map.getInfoWindow().isHidden())
        map.closeInfoWindow();

      infoWindowOpener = this;

      origMethod.apply(this,arguments);
    }
  };

  // go through the openers and install new ones
  var openers = ['openInfoWindow','openInfoWindowTabs','openInfoWindowHtml','openInfoWindowTabsHtml','showMapBlowup'];
  for(var i = 0; i < openers.length; i++) {
    var methodName = openers[i];
    var originalMethod = maker.prototype[methodName];
    if (originalMethod)
      maker.prototype[methodName] = createCallback(originalMethod);
  }

  // hiding the opener makes the infoWindow close
  var originalHideMethod = maker.prototype.hide ? maker.prototype.hide : maker.prototype.prototype.hide;
  maker.prototype.hide = function() {
    if (infoWindowOpener === this)
      map.closeInfoWindow();
      
    originalHideMethod.call(this);
  };

  // make sure that remove closes the infoWindow, if this is the opener
  var originalRemoveMethod = maker.prototype.remove;
  maker.prototype.remove = function() {
    if (infoWindowOpener === this)
      map.closeInfoWindow();

    originalRemoveMethod.call(this);
  };
}
setupGetInfoWindowOpener.version = 0.1;
window.setupGetInfoWindowOpener = setupGetInfoWindowOpener;
})();
/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

  BpLabel - label overlay for Google Maps

These are the methods that should be implemented in this class:
("!" means that it's tested)
! get/setPane
! getMap/isMapped
! show/hide/isVisible
! get/setPoint === get/setLatLng
! getId
! get/setUserData
! getDiv
! copy
! remove
! initialize
! redraw
! get/setCursor
! get/set/resetZIndex/setHighZIndex
! get/setOpacity
! get/setClassName
! maintain event listeners from one addOverlay to another: maintain the same div
! able to hide initially; maintains hidden quality from one add to the next
! getSize/Height/Width
! get/setAnchor
! en/disableDragging/draggingEnabled/getDraggableObject/dragging

TODO:
  openInfoWindow methods

*/

(function() {

var id = 0;

function BpLabel(latlng, html, hide, anchor) {
  GOverlay.apply(this, arguments);

  this._latlng  = latlng  || new GLatLng(0,0);
  this._html    = html    || '';
  this._hide    = hide    || false;
  this._anchor  = anchor  || 'center';

  this._id = ++id;

  this._pane = G_MAP_MARKER_PANE;
  this._cursor = false;
  this._opacity = 80;
  this._userData = {};
  this._className = false;
  this._stale_size = true; // we don't know our size yet
}

BpLabel.prototype = new GOverlay(new GLatLng(0,0));

BpLabel.prototype.getAnchor = function() {
  return this._anchor;
};

BpLabel.prototype.setAnchor = function(anchor) {
  if ((anchor == 'center' || anchor == 'nw') && this._anchor != anchor) {
    this._anchor = anchor;
    this.redraw();
  }
};

// returns only if it's mapped or was mapped and has not changed html or className since.
BpLabel.prototype.getSize = function() {
  if (!this._stale_size) {
    return this._size;
  }

  if(this.isVisible()) {
    var width = this._div.offsetWidth || this._div.style.pixelWidth || 0;
    var height = this._div.offsetHeight || this._div.style.pixelHeight || 0;
    this._size = new GSize(width, height);
    this._stale_size = false;
  }
  else if (this.isMapped()) {
    // put it off-map
    var top  = this._div.style.top;
    var left = this._div.style.left;
    this._div.style.top   = -screen.height + 'px';
    this._div.style.left  = -screen.width  + 'px';

    // show it
    this._div.style.display = '';

    // measure it,
    var width = this._div.offsetWidth || this._div.style.pixelWidth || 0;
    var height = this._div.offsetHeight || this._div.style.pixelHeight || 0;
    this._size = new GSize(width, height);
    this._stale_size = false;

    // hide it
    this._div.style.display = 'none';

    //move it back
    this._div.style.top   = top;
    this._div.style.left  = left;
  }
  // not yet mapped, or the html or className changed since it's been mapped, then return false
  if (this._stale_size) {
    return false;
  }

  return this._size;
};

BpLabel.prototype.getWidth = function() {
  var size = this.getSize();
  if (size) {
    return size.width;
  }
  return false;
};

BpLabel.prototype.getHeight = function() {
  var size = this.getSize();
  if (size) {
    return size.height;
  }
  return false;
};

BpLabel.prototype.setClassName = function(cName) {
  this._stale_size = true;

  this._className = cName;

  if (this._styled) {
    this.removeStyle();
  }

  this._div.className = this._className;

  this.getSize();
};

BpLabel.prototype.getClassName = function() {
  return this._className;
};

BpLabel.defaultStyle = {
  border: '1px solid black',
  backgroundColor: 'white',
  filter: 'alpha(opacity:80)',
  KHTMLOpacity: 0.8,
  MozOpacity: 0.8,
  opacity: 0.8,
  fontWeight: 'bold',
  whiteSpace: 'nowrap',
  paddingRight: '3px',
  paddingLeft: '3px'
};

BpLabel.prototype.addStyle = function() {
  if (this._div) {
    var style = BpLabel.defaultStyle;
    for (var key in style) {
      this._div.style[key] = style[key];
    }

    this._styled = true;
  }
};

BpLabel.prototype.removeStyle = function() {
  if (this._div) {
    var style = BpLabel.defaultStyle;
    for (var key in style) {
      this._div.style[key] = '';
    }

    this._styled = false;
  }
};

BpLabel.prototype.initialize = function(map) {
  this._map = map;

  // if we're doing this for the first time...
  if(!this._div) {
    this._div = document.createElement('div');
    this._div.style.position = 'absolute';

    if (this._className) {
      this._div.className = this._className;
    }
    else {
      this.addStyle();
    }

    this._div.style.zIndex = GOverlay.getZIndex(this.getLatLng().lat());
    this._div.innerHTML = this._html;
  }

  if (this._cursor) {
    this.setCursor(this._cursor);
  }

  this.setOpacity(this._opacity);
  this.setZIndex(this.getZIndex());

  this._div.style.display = 'none';
  map.getPane(this._pane).appendChild(this._div);
  this.getSize();
  this.redraw();

  if (!this._hide) {
    this._div.style.display = '';
  }

  return this._div;
};

BpLabel.prototype.remove = function() {
  if (this._div) {
    this._div.parentNode.removeChild(this._div);
  }
  delete this._map;
};

// this is a bare-bones implementation which anchors on the NW corner
BpLabel.prototype.redraw = function() {
  var divPx = this._map.fromLatLngToDivPixel(this._latlng);

  if (this._anchor == 'center') {
    var size = this.getSize();
    if (size) {
      divPx.x -= Math.round(size.width/2);
      divPx.y -= Math.round(size.height/2);
    }
  }

  this._div.style.left = divPx.x + 'px';
  this._div.style.top  = divPx.y + 'px';
};

// latlng, html, opts (not userData, pane, cursor, opacity, zIndex)
BpLabel.prototype.copy = function() {
  return new BpLabel(this._latlng, this._html, this._hide, this._anchor);
};

BpLabel.prototype.getHtml = function() {
  return this._html;
};

BpLabel.prototype.setHtml = function(html) {
  this._stale_size = true;

  this._html = html;
  if (this._div) {
    this._div.innerHTML = this._html;
  }

  this.getSize();
};

BpLabel.prototype.isMapped = function() {
  return this._map ? true : false;
};

BpLabel.prototype.getMap = function() {
  return this._map;
};

BpLabel.prototype.show = function() {
  this._hide = false;
  if (this._div) {
    this._div.style.display = '';
  }
};

BpLabel.prototype.hide = function() {
  this._hide = true;
  if (this._div) {
    this._div.style.display = 'none';
  }
};

BpLabel.prototype.isVisible = function() {
  if (!this.isMapped() || !this._div || !this._div.parentNode) {
    return false;
  }

  return this._div.style.display != 'none';
};

BpLabel.prototype.isHidden = function() {
  return !this.isVisible();
};

BpLabel.prototype.getUserData = function() {
  return this._userData;
};

BpLabel.prototype.setUserData = function(userData) {
  this._userData = userData;
};

BpLabel.prototype.getZIndex = function() {
  if (! this._div) {
    if (typeof(this._zIndex) != 'undefined') {
      return this._zIndex;
    }
    else {
      return GOverlay.getZIndex(this.getLatLng().lat());
    }
  }

  return this._div.style.zIndex;
};

BpLabel.prototype.setZIndex = function(zIndex) {
  if (arguments.length == 0) {
    this.resetZIndex();
    return;
  }

  this._zIndex = zIndex;

  if (this._div) {
    this._div.style.zIndex = this._zIndex;
  }
};

BpLabel.prototype.resetZIndex = function() {
  delete this._zIndex;
  var zIndex = GOverlay.getZIndex(this.getLatLng().lat());
  this.setZIndex(zIndex);
};

/* "bringToTop/Front" would be better? */
BpLabel.prototype.setHighZIndex = function() {
  var zIndex = GOverlay.getZIndex(-91);
  this._zIndex = zIndex;
  this.setZIndex(zIndex);
};

BpLabel.prototype.getOpacity = function() {
  return this._opacity;
};

BpLabel.prototype.setOpacity = function(opacity) {
  if(opacity < 0)   opacity = 0;
  if(opacity > 100) opacity = 100;

  this._opacity = opacity;

  if(this._div) {
    this._div.style.filter       = 'alpha(opacity:' + this._opacity + ')';
    this._div.style.KHTMLOpacity = this._opacity / 100;
    this._div.style.MozOpacity   = this._opacity / 100;
    this._div.style.opacity      = this._opacity / 100;
  }
};

BpLabel.prototype.getCursor = function() {
  return this._cursor;
};

BpLabel.prototype.setCursor = function(cursor) {
  this._cursor = cursor;

  if(this._div) {
    this._div.style.cursor = this._cursor;
  }
};

BpLabel.prototype.getPane = function() {
  return this._pane;
};

// only works before the overlay is added to the map
BpLabel.prototype.setPane = function(pane) {
  this._pane = pane;
};

BpLabel.prototype.getDiv = function() {
  return this._div;
};

BpLabel.prototype.getLatLng = function() {
  return this._latlng;
};

BpLabel.prototype.setLatLng = function(latlng) {
  this._latlng = latlng;
  if (!this._zIndex) {
    this.resetZIndex();
  }
  this.redraw();
};

BpLabel.prototype.getId = function() {
  return this._id;
};

BpLabel.prototype.getDiv = function() {
  return this._div;
};

BpLabel.prototype.draggingEnabled = function() {
  if (this._draggable) {
    return this._draggable.enabled();
  }
};

BpLabel.prototype.dragging = function() {
  if (this._draggable) {
    return this._draggable.dragging();
  }
};

BpLabel.prototype.disableDragging = function() {
  if (this._draggable) {
    this._draggable.disable();
    return this._draggable;
  }
};

BpLabel.prototype.enableDragging = function() {
  if (this._draggable) {
    this._draggable.enable();
    return this._draggable;
  }

  var label = this;
  var map = label.getMap();
  var draggable = new GDraggableObject(label.getDiv());
  this._draggable = draggable;
  if (label.getCursor()) {
    draggable.setDraggableCursor(label.getCursor());
    draggable.setDraggingCursor('move');
  }

  this._zoomend_listener = GEvent.addListener(map, 'zoomend', function() {
    var point = map.fromLatLngToDivPixel(label.getLatLng());
    if (label.getAnchor() == 'center') {
      point.x -= Math.ceil(label.getWidth() / 2);
      point.y -= Math.ceil(label.getHeight() / 2);
    }
    label._draggable.moveTo(point);
  });

  this._dragend_listener = GEvent.addListener(draggable, 'dragend', function() {
    var left = parseInt(label.getDiv().style.left, 10);
    var top = parseInt(label.getDiv().style.top, 10);

    if (label.getAnchor() == 'center') {
      if (!label.getSize()) {
        return; // we're off the map
      }
      left += Math.ceil(label.getWidth() / 2);
      top += Math.ceil(label.getHeight() / 2);
    }

    var point = new GPoint(left, top);
    var latlng = map.fromDivPixelToLatLng(point);
    label.setLatLng(latlng);
  });

  return draggable;
};

BpLabel.prototype.getDraggableObject = function() {
  return this._draggable;
};

window.BpLabel = BpLabel;
})();
/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

  BpTooltip - a bare-bones tooltip class

TODO:
  test basic sanity of existing code
  change existing tests completely
  manage the current marker & listeners

  future development:
    more window-like functionality?
    clickable?
    popup menu subclass?

*/

(function() {

function BpTooltip(latlng, html) {
  // the true argument will initially hide the label
  BpLabel.call(this, latlng, html, true, 'nw');

  this.setPane(G_MAP_FLOAT_SHADOW_PANE);
  this._bp = {};
  this._bp._ignore = true;
}

BpTooltip.prototype = new BpLabel();

BpTooltip.prototype.initialize = function(map) {
  BpLabel.prototype.initialize.call(this, map);

  // setup the code to reposition the tooltip on zoom
  if (typeof this._onZoomListener == 'undefined') {
    this._onZoomListener = GEvent.addListener(map, 'zoomend', GEvent.callback(this, this.onZoomEnd));
  }

  return this._div;
};

BpTooltip.prototype.onZoomEnd = function() {
  if(this._marker) {
    this.redraw();
  }
};

BpTooltip.prototype.copy = function() {
  return new BpTooltip(this._latlng, this._html);
};

BpTooltip.prototype.setMarker = function(marker) {
  this._marker = marker;
  if(!marker) {
    this.hide();
    this.setHtml('');
  }
  else if (typeof marker.getTooltipHtml === 'function') {
    this.setHtml(marker.getTooltipHtml());
  }
};

BpTooltip.prototype.getMarker = function() {
  return this._marker;
};

// if there's no marker, there's nothing to do
// always force; when the marker moves, we move
BpTooltip.prototype.redraw = function(force) {
  var m = this.getMarker();
  if (m) {
//    this.setHtml( m.getTooltipHtml() );
    m.redraw();
    var point = m.getTooltipPoint();

    this._div.style.top  = point.y + 'px';
    this._div.style.left = point.x + 'px';
  }
};

BpTooltip.prototype.remove = function() {
  if (this._onZoomListener) {
    GEvent.removeListener(this._onZoomListener);
    delete this._onZoomListener;
  }

  BpLabel.prototype.remove.call(this);
};

if (!window.BpTooltip) {
  window.BpTooltip = BpTooltip;
}

})();

/*
Copyright 2005-2009 James Tolley - http://www.bitperfect.com | http://www.gmaptools.com
All rights reserved.
This software is released under the LGPL. (http://www.opensource.org/licenses/lgpl-license.php)

    setup tooltip methods of GMarkers:
    
    marker.setTooltip(BpTooltip_object, html);
    var tt = marker.getTooltip();
    var html = marker.getTooltipHtml();
    marker.setTooltipHtml(html);
    var latlng = marker.getTooltipLatLng();
    marker.setMaintainTooltip(bool);
    marker.removeTooltip();

0.1  - initial release
0.11 - requires manual calling
       optional constructor argument
       better vertical placement in relation to marker: centered

TODO
  use ._bp to hide properties
  the tooltip doesn't show all of itself when the marker is near the top or bottom of the map
  the tooltip will show on the left of the marker if there's not enough room to the right, even if
    there's more room to the right.
    1) count pixels to find out if there's enough room on the right (don't use latlngs)
    2) count pixels to find if there's more room on the right than on the left

*/

if (!window.setupSetTooltip) {

  function mk_bp(m) {
    if (typeof m._bp === 'undefined') {
      m._bp = {};
    }
  }

  window.setupSetTooltip = function(constructor) {
    if (typeof(constructor) != 'function') {
      constructor = GMarker;
    }

    if (typeof constructor.prototype.setTooltip == 'function') {
      return;
    }

    constructor.prototype.removeTooltip = function(){
      mk_bp(this);
      if (this._bp._ttOverListener) {
        GEvent.removeListener(this._bp._ttOverListener);
        delete this._bp._ttOverListener;
        GEvent.removeListener(this._bp._ttOutListener);
        delete this._bp._ttOutListener;
        var tt = this.getTooltip();
        if(tt.getMarker() === this) {
          tt.setMarker();
        }
        delete this._bp._tt;
      }
    }

    constructor.prototype.setTooltip = function(tt, html) {
      mk_bp(this);
      // remove tooltip
      if(!tt && this.getTooltip()) {
        this.removeTooltip();
        return;
      }

      this._bp._tt = tt;
      this._bp._ttHtml = html;
  
      var mouseOverHandler = function() {
        var tt = this.getTooltip();
        if(tt) {
          tt.setMarker(this);
//          tt.setHtml(this._bp._ttHtml);
          tt.redraw();
          tt.show();
        }
      };

      this._bp._ttOverListener = GEvent.addListener(this, 'mouseover', mouseOverHandler);

      var mouseOutHandler = function() {
        var tt = this.getTooltip();
        if(tt && !this._bp._ttMaintain) {
          tt.setMarker();
        }
      };

      this._bp._ttOutListener = GEvent.addListener(this, 'mouseout', mouseOutHandler);
    };

    constructor.prototype.getTooltip = function() {
      mk_bp(this);
      return this._bp._tt;
    };

    constructor.prototype.getTooltipHtml = function() {
      mk_bp(this);
      return this._bp._ttHtml;
    };

    constructor.prototype.setTooltipHtml = function(html) {
      mk_bp(this);
      this._bp._ttHtml = html;
    };

    constructor.prototype.getTooltipPoint = function() {
      mk_bp(this);
      var margin = 5; // px to the right (or left) of the icon

      var tt = this.getTooltip();
      var map = tt.getMap();
      var pixel = map.fromLatLngToDivPixel(this.getLatLng());
      var icon = this.getIcon();
      pixel.x -= icon.iconAnchor.x;
      pixel.x += icon.iconSize.width;
      pixel.x += margin;

      pixel.y -= icon.iconAnchor.y;
      pixel.y += 0.5 * icon.iconSize.height;
      pixel.y -= 0.5 * tt.getHeight();

      // how many pixels are between the margin and the right side of the map?
      var nePixel = map.fromLatLngToDivPixel(map.getBounds().getNorthEast());
      var ttWidth = tt.getWidth();
      if(nePixel.x < pixel.x + ttWidth) {
        pixel.x -= 2 * margin;
        pixel.x -= icon.iconSize.width;
        pixel.x -= ttWidth;
      }
      return pixel;
    };

    constructor.prototype.showTooltip = function() {
      mk_bp(this);
      var tt = this.getTooltip();
      if(!tt) {
        return;
      }
      tt.setMarker(this);
      tt.redraw();
      tt.show();
    };
  
    constructor.prototype.hideTooltip = function() {
      mk_bp(this);
      var tt = this.getTooltip();
      if(!tt) {
        return;
      }
      tt.hide();
    };
  
    constructor.prototype.setMaintainTooltip = function(bool) {
      mk_bp(this);
      this._bp._ttMaintain = bool;
      if(bool) {
        this.showTooltip();
      }
      else {
        this.hideTooltip();
      }
    };
  }
  if (GMarker) {
    setupSetTooltip(GMarker);
  }
  else {
    alert('BpTooltip should be loaded after the Google Maps API');
  }
  setupSetTooltip.version = 0.12;
}/*
  Copyright 2008 James Tolley - bitperfect.com, gmaptools.com
  All rights reserved.
  This code is released under the LGPL. Copy and use it, but keep the copyright notice above intact.

  BpIcon - an easier GIcon

  I can't stand looking around every few months for icon images and trying to remember the various attributes of small icons.
  So I wrote this and collected some images so that I won't have to ever do that busywork again. I hope you like it.

  var icon = new BpIcon(G_DEFAULT_ICON, color);
  var icon = new BpIcon(BP_SMALL_ICON, color);

  var arr = BpIcon.getSmallColors(); // returns a list of the small icon colors that BpIcon can handle automatically.
  var arr = BpIcon.getLargeColors(); // returns a list of the large icon colors that BpIcon can handle automatically.
  
  var url = BpIcon.getImageUrlRoot(); // 'images/icons' by default
  var dir = BpIcon.getSmallImageDir(); // 'small' by default

  // so by default:
  new BpIcon(G_DEFAULT_ICON, 'red'); // assigns image values like 'images/icons/red.png'
  new BpIcon(BP_SMALL_ICON,  'red'); // assigns image values like 'images/icons/small/red.png'

  //pretty much all other icon attributes are handled as you would expect/hope

  // if you have colors that BpIcon does not know about, you can add them - make sure they're in the right directory!
  var small_colors = BpIcon.getSmallColors();
  small_colors.push('tope');
  bpIcon.setSmallColors(small_colors);
  var icon = new BpIcon(BP_SMALL_ICON, 'tope');

  // if you try to create an icon with a color that doesn't have a dot in it, and BpIcon doesn't know about the color, 
  //   you'll get the image for the default icon you used

TODO  - create really good numbered, square, gif icon images - including larger images for hover events
      - create a round Google Maps "non-specific location" marker type: BP_APPROX_ICON
      - maybe create these kinds of default icons for other good icon image sets

*/

(function(){

var small_colors    = ['red', 'blue', 'green', 'yellow', 'white', 'black', 'brown', 'orange', 'purple', 'gray'];

var large_colors    = ['blue', 'brown', 'end', 'gray', 'green', 'orange', 'pause', 'purple', 'red', 'start', 'turquoise', 'yellow',
    'redA', 'redB', 'redC', 'redD', 'redE', 'redF', 'redG', 'redH', 'redI', 'redJ'];

function BpIcon(copy, img) {
  if (copy === G_DEFAULT_ICON && new RegExp('^(' + BpIcon.getLargeColors().join('|') + ')$','i').test(img)) {
    GIcon.call(this, copy, BpIcon.iur + '/' + img + '.png');
    this.printImage = this.mozPrintImage = BpIcon.iur + '/' + img + '.gif';
    return;
  }
  else if (copy === BP_SMALL_ICON && new RegExp('^(' + BpIcon.getSmallColors().join('|') + ')$','i').test(img)) {
    GIcon.call(this, copy, BpIcon.iur + '/' + BpIcon.sdir + '/' + img + '.png');
    this.printImage = this.mozPrintImage = BpIcon.iur + '/' + BpIcon.sdir + '/' + img + '.gif';
    return;
  }

  if (!copy)
    copy = G_DEFAULT_ICON;

  // if this doesn't look like an image url, then they probably are using a color that we don't know about.
  // so just give them the default image instead of nothing
  if (!/\./.test(img))
    img = copy.image;

  GIcon.call(this, copy, img);
}

BpIcon.iur  = 'images/icons';
BpIcon.sdir = 'small';

BpIcon.prototype = new GIcon(G_DEFAULT_ICON);

BpIcon.setImageUrlRoot = function(iur){
  this.iur = iur;
};

BpIcon.getImageUrlRoot = function(iur){
  return this.iur;
};

BpIcon.setSmallImageDir = function(sdir){
  this.sdir = sdir;
};

BpIcon.getSmallImageDir = function(){
  return this.sdir;
};

BpIcon.getSmallColors = function(){
  return small_colors;
};

BpIcon.setSmallColors = function(colors){
  small_colors = colors;
};

BpIcon.getLargeColors = function(){
  return large_colors;
};

BpIcon.setLargeColors = function(colors){
  large_colors = colors;
};

window.BpIcon = BpIcon;

var siur = BpIcon.iur + '/' + BpIcon.sdir + '/';
var BP_SMALL_ICON = new GIcon();
BP_SMALL_ICON.image      = siur + 'red.png';
BP_SMALL_ICON.shadow     = siur + 'shadow.png';
BP_SMALL_ICON.iconSize   = new GSize(12,20);
BP_SMALL_ICON.shadowSize = new GSize(22,20);
BP_SMALL_ICON.iconAnchor = new GPoint(6,20);
BP_SMALL_ICON.imageMap      = [5,0, 3,1, 1,3, 1,7, 4,11, 5,19, 6,19, 7,11, 10,7, 10,3, 8,1, 6,0, 5,0];
BP_SMALL_ICON.printShadow   = siur + 'printShadow.gif';
BP_SMALL_ICON.transparent   = siur + 'transparent.png';
BP_SMALL_ICON.printImage    = siur + 'red.gif';
BP_SMALL_ICON.mozPrintImage = siur + 'red.gif';
BP_SMALL_ICON.infoWindowAnchor = new GPoint(5,1);
BP_SMALL_ICON.infoShadowAnchor = new GPoint(13,13); // ??

window.BP_SMALL_ICON = BP_SMALL_ICON;

/*
!.image
!.shadow
!.iconSize
!.shadowSize
!.iconAnchor
!.infoWindowAnchor
!.transparent
!.imageMap
!.printImage
!.mozPrintImage
!.printShadow
x.maxHeight
x.dragCrossImage
x.dragCrossSize
x.dragCrossAnchor
?.infoShadowAnchor
*/

})();
/*
  this is just handy sometimes:
  
  var line = new BpPolyline(same args);
  var arr  = line.asGLatLngs()
  var arr  = line.asObjects(); // an array of { lat: X, lng: Y }
  var bool = line.isMapped()

  get/setUserData

  var bpline = BpPolyline.cast( gpolyline );
  var bpline = BpPolyline.fromBounds( map.getBounds() );

todo:

*/

(function(){

function BpPolyline() {
  GPolyline.apply(this, arguments);
}

BpPolyline.prototype = new GPolyline([new GLatLng(0,0),new GLatLng(0,0)]);

BpPolyline.prototype.asObjects = function() {
  var a = [];

  for (var i = 0; i < this.getVertexCount(); i++) {
    var ll = this.getVertex(i);
    a.push({
      lat: ll.lat(),
      lng: ll.lng()
    });
  }

  return a;
};

BpPolyline.prototype.asGLatLngs = function() {
  var a = [];
  
  for (var i = 0; i < this.getVertexCount(); i++) {
    a.push(this.getVertex(i));
  }

  return a;
};

BpPolyline.prototype.getPoints = BpPolyline.prototype.asGLatLngs;

BpPolyline.prototype.initialize = function(map) {
  GPolyline.prototype.initialize.call(this, map);
  this._map = map;
};

BpPolyline.prototype.remove = function() {
  GPolyline.prototype.remove.call(this);
  delete this._map;
};

BpPolyline.prototype.isMapped = function() {
  return this._map ? true : false;
};

BpPolyline.prototype.setUserData = function(data) {
  this._data = data;
};

BpPolyline.prototype.getUserData = function() {
  return this._data;
};

// can't/doesn't handle options
BpPolyline.cast = function(line) {
  var points = this.prototype.asGLatLngs.call(line);
  return new this(points, line.color, line.weight, line.opacity);
};
  
BpPolyline.fromBounds = function(bounds, color, weight, opacity, opts) {
  var ne = bounds.getNorthEast();
  var sw = bounds.getSouthWest();
  var nw = new GLatLng(ne.lat(), sw.lng());
  var se = new GLatLng(sw.lat(), ne.lng());

  return new this([ne, nw, sw, se, ne], color, weight, opacity, opts);
};

window.BpPolyline = BpPolyline;
  
})();

/**
  * @fileoverview startAutoUpdate: this will become a method of the map you pass to setupStartAutoUpdate(map), on page load.
  * @requires setOnEndListeners
  * @requires getInViewMarkers
  * @requires getMarkers
  * @requires getOverlays
*/

/*

  This will setup listeners on the map so that zooming in or out, or moving the map will result in markers being added to and
  removed from the map so that all of the markers in view are mapped and all markers out of view are not mapped.

  interface:
    map.startAutoUpdate(url, opts); // all optional opts: markerClass, onZoomIn, onZoomOut, onMoveEnd, onError
    //map.doUpdate();
    //!map.endAutoUpdate(key?);
    map.removeOffViewMarkers();
    GMarker.get/setUserData(d);

  TODO:
    when zooming in, if markers have been taken off the map, and there are fewer than max_mapped_markers mapped,
      get some more markers until max_mapped_markers is reached

    two strategies for managing large numbers of markers:
      clustering -- server-side for a bulletproof solution
      manage mapped marker limits, and fetch markers from the server when zooming in if
          the mapped marker count is lower than the limit
      show the status of the markers displayed: all or first XX
    mapArray, with progress bar
    allow for a single latlng db field instead of two double fields
    disableDragging and disableZooming (cover map div with overlay?)
    allow for multiple database connections: listings, POIs, etc
      store the data according to a key: the url
      doUpdate will take a key as an optional argument... (if there's no key, everything will be updated?)
      map.endAutoUpdate(key?); // all, if no key provided; key provided? only that connection is ended.
    pause/stop auto update for a given feed
    allow for the map to pause reloading for infoWindow-opening pans, and then to "catch up" with the current view
    attach a form (or other search fields) to this to further filter the location records.
    allow for disabled zooming in zoom handlers
    allow the fetching to take place from any data source: allow the user to provide the processing function.

*/

(function(){

/**
  * Creates a startAutoUpdate method on this map.
  *
  * @param {GMap2} map The map to add the method (and various event handlers) to.
  * @param {function} onZoomIn An optional handler to be called onzoomend, when the map is zoomed in.
  * @param {function} onZoomOut An optional handler to be called onzoomend, when the map is zoomed out.
  * @param {function} onMoveEnd An optional handler to be called onmoveend, when the map is not zoomed.
  * @returns A description of the return value.
  *
*/

function setupAddLayer(map) {

  /*
    "this" == the map, called when there's an error downloading the data
  */
  function onDownloadError(json, status) {
    alert('There was an error downloading the data.');
  }
  
  /*
    "this" == the map, it receives the JSON from the server as the first agrument
  */
  function processAutoUpdateData(json, status) {
    if (status != 200) {
      if (typeof this._bpAutoUpdateData.error == 'function') {
        this._bpAutoUpdateData.error.call(this, json, status);
      }
      return;
    }
  
    var data;
    try {
      data = eval(json);
    }
    catch (e) {
      this._bpAutoUpdateData.error.call(this, json, status);
      return;
    }
  
    var markerClass = this._bpAutoUpdateData.markerClass;
    for (var i = 0; i < data.length; i++) {
      var latlng = new GLatLng(parseFloat(data[i].lat), parseFloat(data[i].lng));
      var m = new markerClass(latlng);
      m.setUserData(data[i]);
      this.addOverlay(m);
    }
  }

  map.addLayer = function(url, opts){
    opts = opts || {};
    var mClass        = opts.markerClass ? opts.markerClass : GMarker;
    var onErrorFn     = opts.onError     ? opts.onError     : onDownloadError;

    if (typeof mClass.getUserData != 'function' && typeof mClass.setUserData != 'function') {
      mClass.prototype.getUserData = function(){  return this._bpUserData; };
      mClass.prototype.setUserData = function(d){ this._bpUserData = d;    };
    }

    this._bpAutoUpdateData = {
      url:          url,
      markerClass:  mClass,
      error:        onErrorFn
    };

    this.setOnEndListeners({
      zoomIn: function(oldZoom,newZoom) {
        // manage self state
        this._bpAutoUpdateData.previousBounds = this.getBounds();

        // manage markers
        this.removeOffViewMarkers();
      },
      zoomOut: function(oldZoom,newZoom) {
        this.doUpdate();
      },
      moveEnd: function() {
        // manage markers
        this.doUpdate();
        this.removeOffViewMarkers();
      }
    });

    this.doUpdate();
    return true;
  };

  map.doUpdate = function() {
    var b  = this.getBounds();
    var ne = b.getNorthEast();
    var sw = b.getSouthWest();
    var qs = '?bottom=' + sw.lat() + '&top=' + ne.lat() + '&left=' + sw.lng() + '&right=' + ne.lng();
    if (this._bpAutoUpdateData.previousBounds) {
      var pb = this._bpAutoUpdateData.previousBounds;
      var pne = pb.getNorthEast();
      var psw = pb.getSouthWest();
      qs += '&nobottom=' + psw.lat() + '&notop=' + pne.lat() + '&noleft=' + psw.lng() + '&noright=' + pne.lng();
    }
    GDownloadUrl(this._bpAutoUpdateData.url + qs, GEvent.callback(this, processAutoUpdateData));
    this._bpAutoUpdateData.previousBounds = b;
  };

  map.removeOffViewMarkers = function() {
    var b = this.getBounds();
    var m = this.getOverlays(GMarker, false); // off-view GMarker instances
    for (var i = 0; i < m.length; i++) {
      this.removeOverlay(m[i]);
    }
  };
}

window.setupAddLayer = setupAddLayer;

})();


window.BpMap = BpMap;
})();


