

        // If FireBug/Safari isn't running
        if (!window.console) window.console = {log: function(x) {}};

        function newNode(tagName) {
          return document.createElement(tagName);
        }

        function getNode(id) {
          return document.getElementById(id);
        }

        function agentHas(s) {
          return navigator.userAgent.toLowerCase().indexOf(s) != -1
        }

        function isSafari() {
          return agentHas("safari");
        }

        function isGecko() {
          return agentHas("gecko") && !isSafari()
        }

        function isMsie() {
          return agentHas("msie") && !window.opera;
        }

        function hasClass(node, className) {
          return className in getClassMap(node);
        }

        function addClass(node, className) {
          if (hasClass(node, className)) return;
          
          node.className += " " + className;
        }

        function removeClass(node, className) {
          var classMap = getClassMap(node);

          if (!(className in classMap)) return;
          
          delete classMap[className];
          var newClassList = [];
          
          for (var className in classMap) {
            newClassList.push(className);
          }
          
          node.className = newClassList.join(" ");
        }

        function getClassMap(node) {
          var classMap = {};
          var classNames = node.className.split(/\s+/);
          
          for (var i = 0; i < classNames.length; i++) {
            classMap[classNames[i]] = true;
          }
          
          return classMap;
        }

        var tokenCache = {};

        function tokenize(s) {
          if (!(s in tokenCache)) {
            var tokens = s.toLowerCase().split(/[ !.]+/);
            var tokenMap = {};
            
            for (var i = 0; i < tokens.length; i++) {
              var token = tokens[i];
              
              token = token.replace(/[^A-Za-z0-9]+/g, "");
              tokenMap[token] = true;
            }
            
            tokenCache[s] = tokenMap;
          }
          
          return tokenCache[s];
        }

        var tokenizationQueue = [];
        var tokenizationTimeout = null;

        function addToTokenCache(s) {
          tokenizationQueue.push(s);
          
          if (!tokenizationTimeout) {
            tokenizationTimeout = window.setTimeout(function() {
              for (var i = 0; i < tokenizationQueue.length; i++) {
                tokenize(tokenizationQueue[i]);
              }
              
              tokenizationQueue = [];
              tokenizationTimeout = null;
            }, 5000);
          }
        }



        // Classes.js
        function QuoteSet(location, type, detailID, locationName, lat, lng, color) {
            // rjb Added locationName and type variable
          this.location = location;
          this.type = type
          this.detailID = detailID;
          this.locationName = locationName;
          this.lat = lat;
          this.lng = lng;
          this.color = color;
          this.point = new GLatLng(this.lat, this.lng);
          this.iconNode = null;
          this.infoWindowNode = null;
        }

        QuoteSet.prototype.getPoint = function() {
          return this.point;
        }

        QuoteSet.prototype.getIconNode = function() {
          if (!this.iconNode) 
          {
            this.iconNode = newNode("div");  
            // rjb
            //var className = "marker";
            // Show which hikes the specified user did using different color markers.
            var className;
            className = "marker" + this.color;
            this.iconNode.className = className;
            this.iconNode.quoteSet = this;
            this.iconNode.setAttribute('tiptitle', this.locationName);
	        this.iconNode.onmouseover = function() {
              tooltip.show(this.getAttribute('tiptitle'));
	        };
	        this.iconNode.onmouseout = function() {tooltip.hide()};
          }
          
          return this.iconNode;
        }

        QuoteSet.prototype.getIconNodeBounds = function() {
          var tl = new GPoint(this.iconNode.offsetLeft, this.iconNode.offsetTop);
          var br = new GPoint(tl.x + this.iconNode.offsetWidth, tl.y + this.iconNode.offsetHeight);
          
          return new GBounds([tl, br]);
        }

        QuoteSet.prototype.getQuotes = function() {
          if (this.matchedSearch) {
            return this.matchingQuotes;
          } else {
            return this.quotes;
          }
        }

        QuoteSet.prototype.showInfoWindow = function() {
            // See if we can come up with a way to open a new window here.
            window.open("/Locations/ShowLocation.aspx?LocationID=" + this.detailID);
            // The original way.   rjb
            //map.closeInfoWindow();
            //map.openInfoWindow(this.point, this.getInfoWindowNode());
        }

        QuoteSet.prototype.getInfoWindowNode = function() {
          if (!this.infoWindowNode) {
            this.infoWindowNode = newNode("iframe");
            window.open("/Locations/ShowLocation.aspx?LocationID=" + this.detailID);
            this.infoWindowNode.frameBorder = 0;
          }
          
          if (!map.getInfoWindow().isHidden()) {
              this.infoWindowNode.style.display = "none";
              map.openInfoWindow(this.point,
                                 this.infoWindowNode);
              this.infoWindowNode.style.display = "";
           }
          
          return this.infoWindowNode;
        }

        function QuotesOverlay(quotes) {
          GOverlay.call(this);
          
          this.map = null;
          this.quotes = quotes;
          this.visibleQuotes = {};
        }

        QuotesOverlay.prototype.initialize = function(map) {
          this.map = map;
          this.parentNode = map.getPane(G_MAP_MARKER_PANE);
          
          // To handle clicks in the shadow, adding a simple handler to
          // G_MAP_MARKER_MOUSE_TARGET_PANE doesn't seem to work, and we don't want
          // to have to add DOM nodes there too. So instead we add a global click
          // handler to the map that looks for clicked markers if the regular
          // handler doesn't trigger
          GEvent.bindDom(this.parentNode, "click", this, this.handleDomClick);
          GEvent.bind(map, "click", this, this.handleMapClick);  
          GEvent.bind(map, "dragstart", this, this.beginMapDrag);
          GEvent.bind(map, "dragend", this, this.endMapDrag);
        }

        QuotesOverlay.prototype.beginMapDrag = function() {
          // Don't count short drags, so that they still trigger marker clicks
          var self = this;
          this.beginDragTimeout = window.setTimeout(function() {
            self.beginDragTimeout = null;
            self.inDrag = true;
          }, 250);
        }

        QuotesOverlay.prototype.endMapDrag = function() {
          if (this.beginDragTimeout) {
            window.clearTimeout(this.beginDragTimeout);
          } else {
            // We reset the drag state in a timeout because we want the click event
            // (if any) to be processed first
            var self = this;
            window.setTimeout(function() {
              self.inDrag = false;
            }, 0);
          }
        }

        QuotesOverlay.prototype.handleMapClick = function(marker, point) {
          var self = this;
          this.handledClick = false;
          window.setTimeout(function() {
            if (self.handledClick) return;
            
            var domPoint = map.fromLatLngToDivPixel(point);
            var domBounds = new GBounds([domPoint]);
            
            for (var location in self.visibleQuotes) {
              var quote = self.visibleQuotes[location];
              
              if (quote.getIconNodeBounds().containsBounds(domBounds)) {
                self.handleDomClick({target: quote.getIconNode()});
                break;
              }
            }
          }, 0);
        }

        QuotesOverlay.prototype.handleDomClick = function(event) {
          this.handledClick = true;
          
          if (this.inDrag) {
            return;
          }
          
          for (var node = event.target; node; node = node.parentNode) {  
            if (node.quoteSet) {
              node.quoteSet.showInfoWindow();
              break;
            }
          }
        }

        QuotesOverlay.prototype.remove = function() {
          window.console.log("TODO: removing");
        }

        QuotesOverlay.prototype.copy = function() {
          window.console.log("TODO: copying");
          
          return this;
        }

        QuotesOverlay.prototype.redraw = function(force) {
          if (force) {
            var zoom = map.getZoom();
            
            addClass(containerNode, "quotes");
            
            this.resetVisibleQuotes();        
          } else {
            if (this.updateVisibleQuotesTimeout) {
              window.clearTimeout(this.updateVisibleQuotesTimeout);
            }
            var self = this;
            this.updateVisibleQuotesTimeout = window.setTimeout(function() {
              self.updateVisibleQuotesTimeout = null;
              self.updateVisibleQuotes();
            }, 100);
          }
        }

        QuotesOverlay.prototype.resetVisibleQuotes = function() {
          for (var location in this.visibleQuotes) {
            var quote = this.visibleQuotes[location];
            this.parentNode.removeChild(quote.getIconNode());
          }
          
          this.visibleQuotes = {};
          
          this.updateVisibleQuotes();
        }

        QuotesOverlay.prototype.updateVisibleQuotes = function() {
          var start = new Date().getTime();

          var quotesToRemove = [];
          var quotesToAdd = [];
          var newVisibleQuotes = {};
          var mapBounds = this.map.getBounds();
          
          // Enlarge bounds a bit so points at the edges don't flicker in and out
          var sw = mapBounds.getSouthWest();
          var ne = mapBounds.getNorthEast();
          
          sw = new GLatLng(sw.lat() - 0.001, sw.lng() - 0.001); 
          ne = new GLatLng(ne.lat() + 0.001, ne.lng() + 0.001);
          
          mapBounds = new GLatLngBounds(sw, ne);

          zoom = map.getZoom();
          
          visibleCount = 0;
          for (var i = 0, quoteSet; quoteSet = this.quotes[i]; i++) {
            var alreadyVisible = quoteSet.location in this.visibleQuotes;
            
            if (mapBounds.contains(quoteSet.getPoint())) 
	        {
              visibleCount++;
              newVisibleQuotes[quoteSet.location] = quoteSet;
              if (!alreadyVisible) 
	          {
                quotesToAdd.push(quoteSet);
              }
            } 
	        else if (alreadyVisible) 
	        {
              quotesToRemove.push(quoteSet);
            }
          }
          
          var removeStart = new Date().getTime();
          
          for (var i = 0, quoteSet; quoteSet = quotesToRemove[i]; i++) {
            this.parentNode.removeChild(quoteSet.getIconNode());
          }
          
          this.visibleQuotes = newVisibleQuotes;
          
          var addStart = new Date().getTime();

          for (var i = 0, quoteSet; quoteSet = quotesToAdd[i]; i++) {
            var iconNode = quoteSet.getIconNode();
            var iconPosition = this.map.fromLatLngToDivPixel(quoteSet.getPoint());
            
            iconNode.style.left = iconPosition.x + "px";
            iconNode.style.top = iconPosition.y + "px";
            
            this.parentNode.appendChild(iconNode);
          }
          
          var end = new Date().getTime()
          
          window.console.log(
            "showing " + visibleCount + "/" + quotes.length + 
                " (" + (removeStart - start) + "ms)" +
            " added " + quotesToAdd.length + 
                " (" + (end - addStart) + "ms)" +
            " removed " + quotesToRemove.length +
                " (" + (addStart - removeStart) + "ms)");
        }



	    // src="scripts.js"
        //////////////////////////////////////////////////////////////////
        // qTip - CSS Tool Tips - by Craig Erskine
        // http://qrayg.com | http://solardreamstudios.com
        //
        // Inspired by code from Travis Beckham
        // http://www.squidfingers.com | http://www.podlob.com
        //////////////////////////////////////////////////////////////////
        


        var qTipTag = "a"; //Which tag do you want to qTip-ize? Keep it lowercase!//
        var qTipX = 20 //This is qTip's X offset//
        var qTipY = 20; //This is qTip's Y offset//
        

        //There's No need to edit anything below this line//
        tooltip = {
          name : "qTip",
          offsetX : qTipX,
          offsetY : qTipY,
          tip : null
        }
        
        tooltip.init = function () {
	        var tipNameSpaceURI = "http://www.w3.org/1999/xhtml";
	        if(!tipContainerID){ var tipContainerID = "qTip";}
	        var tipContainer = document.getElementById(tipContainerID);

	        if(!tipContainer) {
	          tipContainer = document.createElementNS ? document.createElementNS(tipNameSpaceURI, "div") : document.createElement("div");
		        tipContainer.setAttribute("id", tipContainerID);
	          document.getElementsByTagName("body").item(0).appendChild(tipContainer);
	        }

	        if (!document.getElementById) return;
	        this.tip = document.getElementById (this.name);
	        if (this.tip) document.onmousemove = function (evt) {tooltip.move (evt)};

	        var a, sTitle;
	        var anchors = document.getElementsByTagName (qTipTag);

	        for (var i = 0; i < anchors.length; i ++) {
		        a = anchors[i];
		        sTitle = a.getAttribute("title");
		        if(sTitle) {
			        a.setAttribute("tiptitle", sTitle);
			        a.removeAttribute("title");
			        a.onmouseover = function() {tooltip.show(this.getAttribute('tiptitle'))};
			        a.onmouseout = function() {tooltip.hide()};
		        }
	        }
        }
        
        tooltip.move = function (evt) {
	        var x=0, y=0;
	        if (document.all) {//IE
		        x = (document.documentElement && document.documentElement.scrollLeft) ? document.documentElement.scrollLeft : document.body.scrollLeft;
		        y = (document.documentElement && document.documentElement.scrollTop) ? document.documentElement.scrollTop : document.body.scrollTop;
		        x += window.event.clientX;
		        y += window.event.clientY;
        		
	        } else {//Good Browsers
		        x = evt.pageX;
		        y = evt.pageY;
	        }
	        this.tip.style.left = (x + this.offsetX) + "px";
	        this.tip.style.top = (y + this.offsetY) + "px";
        }
        
        tooltip.show = function (text) {
	        if (!this.tip) return;
	        this.tip.innerHTML = text;
	        this.tip.style.display = "block";
        }
        
        tooltip.hide = function () {
	        if (!this.tip) return;
	        this.tip.innerHTML = "";
	        this.tip.style.display = "none";
        }
