//     Underscore.js 1.7.0
//     http://underscorejs.org
//     (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Underscore may be freely distributed under the MIT license.
(function(){var n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}};h.iteratee=function(n,t,r){return null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return a};var v="Reduce of empty array with no initial value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return r},h.find=h.detect=function(n,t,r){var e;return t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 0}),e},h.filter=h.select=function(n,t,r){var e=[];return null==n?e:(t=h.iteratee(t,r),h.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var r=a.call(arguments,2),e=h.isFunction(t);return h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return h.map(n,h.property(t))},h.where=function(n,t){return h.filter(n,h.matches(t))},h.findWhere=function(n,t){return h.find(n,h.matches(t))},h.max=function(n,t,r){var e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return i},h.min=function(n,t,r){var e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return i},h.shuffle=function(n){for(var t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},h.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=function(n){return function(t,r,e){var u={};return r=h.iteratee(r,e),h.each(t,function(e,i){var a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])<u?i=o+1:a=o}return i},h.toArray=function(n){return n?h.isArray(n)?a.call(n):n.length===+n.length?h.map(n,h.identity):h.values(n):[]},h.size=function(n){return null==n?0:n.length===+n.length?n.length:h.keys(n).length},h.partition=function(n,t,r){t=h.iteratee(t,r);var e=[],u=[];return h.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},h.first=h.head=h.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:0>t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.length-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return a.call(n,null==t||r?1:t)},h.compact=function(n){return h.filter(n,h.identity)};var y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var u=0,a=n.length;a>u;u++){var l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!h.contains(t,i)){for(var a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.push(i)}}return t},h.difference=function(n){var t=y(a.call(arguments,1),!0,!0,[]);return h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return r},h.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},h.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return p.apply(n,a.call(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r<arguments.length;)e.push(arguments[r++]);return n.apply(this,e)}},h.bindAll=function(n){var t,r,e=arguments.length;if(1>=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return n},h.memoize=function(n,t){var r=function(e){var u=r.cache,i=t?t.apply(this,arguments):e;return h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},h.defer=function(n){return h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var e,u,i,a,o,l=function(){var c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,a=h.now();var c=r&&!e;return e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return h.partial(t,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},h.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return t},h.values=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},h.pairs=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},h.invert=function(n){for(var t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},h.functions=h.methods=function(n){var t=[];for(var r in n)h.isFunction(n[r])&&t.push(r);return t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(arguments,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in n&&(u[e]=n[e])}return u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 0&&(n[u]=e[u])}return n},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return t(n),n};var b=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]===n)return e[i]===t;var a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c,f;if("[object Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return r.pop(),e.pop(),f};h.isEqual=function(n,t){return b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return 0===n.length;for(var t in n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object Array]"===l.call(n)},h.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return l.call(t)==="[object "+n+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===l.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return n===void 0},h.has=function(n,t){return null!=n&&c.call(n,t)},h.noConflict=function(){return n._=t,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(n){return function(t){return t[n]}},h.matches=function(n){var t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in n))return!1}return!0}},h.times=function(n,t,r){var e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},h.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var _={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},w=h.invert(_),j=function(n){var t=function(t){return n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var x=0;h.uniqueId=function(n){var t=++x+"";return n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw o.source=i,o}var l=function(n){return a.call(this,n,h)},c=t.variable||"obj";return l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return t._chain=!0,t};var E=function(n){return this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=r[n];h.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var t=r[n];h.prototype[n]=function(){return E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}).call(this);
//# sourceMappingURL=underscore-min.map;
// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js
// ==/ClosureCompiler==

/**
* @name CSS3 InfoBubble with tabs for Google Maps API V3
* @version 0.8
* @author Luke Mahe
* @fileoverview
* This library is a CSS Infobubble with tabs. It uses css3 rounded corners and
* drop shadows and animations. It also allows tabs
*/

/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/**
* A CSS3 InfoBubble v0.8
* @param {Object.<string, *>=} opt_options Optional properties to set.
* @extends {google.maps.OverlayView}
* @constructor
*/
function InfoBubble(opt_options) {
    this.tabs_ = [];
    this.activeTab_ = null;
    this.baseZIndex_ = 100;
    this.isOpen_ = false;

    var options = opt_options || {};

    if (options['backgroundColor'] == undefined) {
        options['backgroundColor'] = this.BACKGROUND_COLOR_;
    }

    if (options['borderColor'] == undefined) {
        options['borderColor'] = this.BORDER_COLOR_;
    }

    if (options['borderRadius'] == undefined) {
        options['borderRadius'] = this.BORDER_RADIUS_;
    }

    if (options['borderWidth'] == undefined) {
        options['borderWidth'] = this.BORDER_WIDTH_;
    }

    if (options['padding'] == undefined) {
        options['padding'] = this.PADDING_;
    }

    if (options['arrowPosition'] == undefined) {
        options['arrowPosition'] = this.ARROW_POSITION_;
    }

    if (options['disableAutoPan'] == undefined) {
        options['disableAutoPan'] = false;
    }

    if (options['disableAnimation'] == undefined) {
        options['disableAnimation'] = false;
    }

    if (options['minWidth'] == undefined) {
        options['minWidth'] = this.MIN_WIDTH_;
    }

    if (options['shadowStyle'] == undefined) {
        options['shadowStyle'] = this.SHADOW_STYLE_;
    }

    if (options['arrowSize'] == undefined) {
        options['arrowSize'] = this.ARROW_SIZE_;
    }

    if (options['arrowStyle'] == undefined) {
        options['arrowStyle'] = this.ARROW_STYLE_;
    }

    this.buildDom_();

    this.setValues(options);
}
InfoBubble.prototype = new google.maps.OverlayView();
window['InfoBubble'] = InfoBubble;


/**
* Default arrow size
* @const
* @private
*/
InfoBubble.prototype.ARROW_SIZE_ = 15;


/**
* Default arrow style
* @const
* @private
*/
InfoBubble.prototype.ARROW_STYLE_ = 0;


/**
* Default shadow style
* @const
* @private
*/
InfoBubble.prototype.SHADOW_STYLE_ = 1;


/**
* Default min width
* @const
* @private
*/
InfoBubble.prototype.MIN_WIDTH_ = 50;


/**
* Default arrow position
* @const
* @private
*/
InfoBubble.prototype.ARROW_POSITION_ = 50;


/**
* Default padding
* @const
* @private
*/
InfoBubble.prototype.PADDING_ = 10;


/**
* Default border width
* @const
* @private
*/
InfoBubble.prototype.BORDER_WIDTH_ = 1;


/**
* Default border color
* @const
* @private
*/
InfoBubble.prototype.BORDER_COLOR_ = '#ccc';


/**
* Default border radius
* @const
* @private
*/
InfoBubble.prototype.BORDER_RADIUS_ = 10;


/**
* Default background color
* @const
* @private
*/
InfoBubble.prototype.BACKGROUND_COLOR_ = '#fff';


/**
* Builds the InfoBubble dom
* @private
*/
InfoBubble.prototype.buildDom_ = function () {
    var bubble = this.bubble_ = document.createElement('DIV');
    bubble.style['position'] = 'absolute';
    bubble.style['zIndex'] = this.baseZIndex_;

    var tabsContainer = this.tabsContainer_ = document.createElement('DIV');
    tabsContainer.style['position'] = 'relative';

    // Close button
    var close = this.close_ = document.createElement('IMG');
    close.style['position'] = 'absolute';
    close.style['width'] = this.px(12);
    //close.style['height'] = this.px(12);
    close.style['border'] = 0;
    close.style['zIndex'] = this.baseZIndex_ + 1;
    close.style['cursor'] = 'pointer';
    close.src = 'http://maps.gstatic.com/intl/en_us/mapfiles/iw_close.gif';

    var that = this;
    google.maps.event.addDomListener(close, 'click', function () {
        that.close();
    });

    // Content area
    var contentContainer = this.contentContainer_ = document.createElement('DIV');
    //contentContainer.style['overflowX'] = 'auto';
    //contentContainer.style['overflowY'] = 'auto';
    contentContainer.style['cursor'] = 'default';
    contentContainer.style['clear'] = 'both';
    contentContainer.style['position'] = 'relative';

    var content = this.content_ = document.createElement('DIV');
    contentContainer.appendChild(content);

    // Arrow
    var arrow = this.arrow_ = document.createElement('DIV');
    arrow.style['position'] = 'relative';

    var arrowOuter = this.arrowOuter_ = document.createElement('DIV');
    var arrowInner = this.arrowInner_ = document.createElement('DIV');

    var arrowSize = this.getArrowSize_();

    arrowOuter.style['position'] = arrowInner.style['position'] = 'absolute';
    arrowOuter.style['left'] = arrowInner.style['left'] = '50%';
    //arrowOuter.style['height'] = arrowInner.style['height'] = '0';
    arrowOuter.style['width'] = arrowInner.style['width'] = '0';
    arrowOuter.style['marginLeft'] = this.px(-arrowSize);
    arrowOuter.style['borderWidth'] = this.px(arrowSize);
    arrowOuter.style['borderBottomWidth'] = 0;

    // Shadow
    var bubbleShadow = this.bubbleShadow_ = document.createElement('DIV');
    bubbleShadow.style['position'] = 'absolute';

    // Hide the InfoBubble by default
    bubble.style['display'] = bubbleShadow.style['display'] = 'none';

    bubble.appendChild(this.tabsContainer_);
    bubble.appendChild(close);
    bubble.appendChild(contentContainer);
    arrow.appendChild(arrowOuter);
    arrow.appendChild(arrowInner);
    bubble.appendChild(arrow);

    var stylesheet = document.createElement('style');
    stylesheet.setAttribute('type', 'text/css');

    /**
    * The animation for the infobubble
    * @type {string}
    */
    this.animationName_ = '_ibani_' + Math.round(Math.random() * 10000);

    var css = '.' + this.animationName_ + '{-webkit-animation-name:' +
      this.animationName_ + ';-webkit-animation-duration:0.5s;' +
      '-webkit-animation-iteration-count:1;}' +
      '@-webkit-keyframes ' + this.animationName_ + ' {from {' +
      '-webkit-transform: scale(0)}50% {-webkit-transform: scale(1.2)}90% ' +
      '{-webkit-transform: scale(0.95)}to {-webkit-transform: scale(1)}}';

    stylesheet.textContent = css;
    document.getElementsByTagName('head')[0].appendChild(stylesheet);
};


/**
* Sets the background class name
*
* @param {string} className The class name to set.
*/
InfoBubble.prototype.setBackgroundClassName = function (className) {
    this.set('backgroundClassName', className);
};
InfoBubble.prototype['setBackgroundClassName'] =
    InfoBubble.prototype.setBackgroundClassName;


/**
* changed MVC callback
*/
InfoBubble.prototype.backgroundClassName_changed = function () {
    this.content_.className = this.get('backgroundClassName');
};
InfoBubble.prototype['backgroundClassName_changed'] =
    InfoBubble.prototype.backgroundClassName_changed;


/**
* Sets the class of the tab
*
* @param {string} className the class name to set.
*/
InfoBubble.prototype.setTabClassName = function (className) {
    this.set('tabClassName', className);
};
InfoBubble.prototype['setTabClassName'] =
    InfoBubble.prototype.setTabClassName;


/**
* tabClassName changed MVC callback
*/
InfoBubble.prototype.tabClassName_changed = function () {
    this.updateTabStyles_();
};
InfoBubble.prototype['tabClassName_changed'] =
    InfoBubble.prototype.tabClassName_changed;


/**
* Gets the style of the arrow
*
* @private
* @return {number} The style of the arrow.
*/
InfoBubble.prototype.getArrowStyle_ = function () {
    return parseInt(this.get('arrowStyle'), 10) || 0;
};


/**
* Sets the style of the arrow
*
* @param {number} style The style of the arrow.
*/
InfoBubble.prototype.setArrowStyle = function (style) {
    this.set('arrowStyle', style);
};
InfoBubble.prototype['setArrowStyle'] =
    InfoBubble.prototype.setArrowStyle;


/**
* Arrow style changed MVC callback
*/
InfoBubble.prototype.arrowStyle_changed = function () {
    this.arrowSize_changed();
};
InfoBubble.prototype['arrowStyle_changed'] =
    InfoBubble.prototype.arrowStyle_changed;


/**
* Gets the size of the arrow
*
* @private
* @return {number} The size of the arrow.
*/
InfoBubble.prototype.getArrowSize_ = function () {
    return parseInt(this.get('arrowSize'), 10) || 0;
};


/**
* Sets the size of the arrow
*
* @param {number} size The size of the arrow.
*/
InfoBubble.prototype.setArrowSize = function (size) {
    this.set('arrowSize', size);
};
InfoBubble.prototype['setArrowSize'] =
    InfoBubble.prototype.setArrowSize;


/**
* Arrow size changed MVC callback
*/
InfoBubble.prototype.arrowSize_changed = function () {
    this.borderWidth_changed();
};
InfoBubble.prototype['arrowSize_changed'] =
    InfoBubble.prototype.arrowSize_changed;


/**
* Set the position of the InfoBubble arrow
*
* @param {number} pos The position to set.
*/
InfoBubble.prototype.setArrowPosition = function (pos) {
    this.set('arrowPosition', pos);
};
InfoBubble.prototype['setArrowPosition'] =
    InfoBubble.prototype.setArrowPosition;


/**
* Get the position of the InfoBubble arrow
*
* @private
* @return {number} The position..
*/
InfoBubble.prototype.getArrowPosition_ = function () {
    return parseInt(this.get('arrowPosition'), 10) || 0;
};


/**
* arrowPosition changed MVC callback
*/
InfoBubble.prototype.arrowPosition_changed = function () {
    var pos = this.getArrowPosition_();
    this.arrowOuter_.style['left'] = this.arrowInner_.style['left'] = pos + '%';

    this.redraw_();
};
InfoBubble.prototype['arrowPosition_changed'] =
    InfoBubble.prototype.arrowPosition_changed;


/**
* Set the zIndex of the InfoBubble
*
* @param {number} zIndex The zIndex to set.
*/
InfoBubble.prototype.setZIndex = function (zIndex) {
    this.set('zIndex', zIndex);
};
InfoBubble.prototype['setZIndex'] = InfoBubble.prototype.setZIndex;


/**
* Get the zIndex of the InfoBubble
*
* @return {number} The zIndex to set.
*/
InfoBubble.prototype.getZIndex = function () {
    return parseInt(this.get('zIndex'), 10) || this.baseZIndex_;
};


/**
* zIndex changed MVC callback
*/
InfoBubble.prototype.zIndex_changed = function () {
    var zIndex = this.getZIndex_();

    this.bubble_.style['zIndex'] = this.baseZIndex_ = zIndex;
    this.close_.style['zIndex'] = zIndex_ + 1;
};
InfoBubble.prototype['zIndex_changed'] = InfoBubble.prototype.zIndex_changed;


/**
* Set the style of the shadow
*
* @param {number} shadowStyle The style of the shadow.
*/
InfoBubble.prototype.setShadowStyle = function (shadowStyle) {
    this.set('shadowStyle', shadowStyle);
};
InfoBubble.prototype['setShadowStyle'] = InfoBubble.prototype.setShadowStyle;


/**
* Get the style of the shadow
*
* @private
* @return {number} The style of the shadow.
*/
InfoBubble.prototype.getShadowStyle_ = function () {
    return parseInt(this.get('shadowStyle'), 10) || 0;
};


/**
* shadowStyle changed MVC callback
*/
InfoBubble.prototype.shadowStyle_changed = function () {
    var shadowStyle = this.getShadowStyle_();

    var display = '';
    var shadow = '';
    var backgroundColor = '';
    switch (shadowStyle) {
        case 0:
            display = 'none';
            break;
        case 1:
            shadow = '40px 15px 10px rgba(33,33,33,0.3)';
            backgroundColor = 'transparent';
            break;
        case 2:
            shadow = '0 0 2px rgba(33,33,33,0.3)';
            backgroundColor = 'rgba(33,33,33,0.35)';
            break;
    }
    this.bubbleShadow_.style['boxShadow'] =
      this.bubbleShadow_.style['webkitBoxShadow'] =
      this.bubbleShadow_.style['MozBoxShadow'] = shadow;
    this.bubbleShadow_.style['backgroundColor'] = backgroundColor;
    this.bubbleShadow_.style['display'] = display;
    this.draw();
};
InfoBubble.prototype['shadowStyle_changed'] =
    InfoBubble.prototype.shadowStyle_changed;


/**
* Show the close button
*/
InfoBubble.prototype.showCloseButton = function () {
    this.set('hideCloseButton', false);
};
InfoBubble.prototype['showCloseButton'] = InfoBubble.prototype.showCloseButton;


/**
* Hide the close button
*/
InfoBubble.prototype.hideCloseButton = function () {
    this.set('hideCloseButton', true);
};
InfoBubble.prototype['hideCloseButton'] = InfoBubble.prototype.hideCloseButton;


/**
* hideCloseButton changed MVC callback
*/
InfoBubble.prototype.hideCloseButton_changed = function () {
    this.close_.style['display'] = this.get('hideCloseButton') ? 'none' : '';
};
InfoBubble.prototype['hideCloseButton_changed'] =
    InfoBubble.prototype.hideCloseButton_changed;


/**
* Set the background color
*
* @param {string} color The color to set.
*/
InfoBubble.prototype.setBackgroundColor = function (color) {
    if (color) {
        this.set('backgroundColor', color);
    }
};
InfoBubble.prototype['setBackgroundColor'] =
    InfoBubble.prototype.setBackgroundColor;


/**
* backgroundColor changed MVC callback
*/
InfoBubble.prototype.backgroundColor_changed = function () {
    var backgroundColor = this.get('backgroundColor');
    this.contentContainer_.style['backgroundColor'] = backgroundColor;

    this.arrowInner_.style['borderColor'] = backgroundColor +
      ' transparent transparent';
    this.updateTabStyles_();
};
InfoBubble.prototype['backgroundColor_changed'] =
    InfoBubble.prototype.backgroundColor_changed;


/**
* Set the border color
*
* @param {string} color The border color.
*/
InfoBubble.prototype.setBorderColor = function (color) {
    if (color) {
        this.set('borderColor', color);
    }
};
InfoBubble.prototype['setBorderColor'] = InfoBubble.prototype.setBorderColor;


/**
* borderColor changed MVC callback
*/
InfoBubble.prototype.borderColor_changed = function () {
    var borderColor = this.get('borderColor');

    var contentContainer = this.contentContainer_;
    var arrowOuter = this.arrowOuter_;
    contentContainer.style['borderColor'] = borderColor;

    arrowOuter.style['borderColor'] = borderColor +
      ' transparent transparent';

    contentContainer.style['borderStyle'] =
      arrowOuter.style['borderStyle'] =
      this.arrowInner_.style['borderStyle'] = 'solid';

    this.updateTabStyles_();
};
InfoBubble.prototype['borderColor_changed'] =
    InfoBubble.prototype.borderColor_changed;


/**
* Set the radius of the border
*
* @param {number} radius The radius of the border.
*/
InfoBubble.prototype.setBorderRadius = function (radius) {
    this.set('borderRadius', radius);
};
InfoBubble.prototype['setBorderRadius'] = InfoBubble.prototype.setBorderRadius;


/**
* Get the radius of the border
*
* @private
* @return {number} The radius of the border.
*/
InfoBubble.prototype.getBorderRadius_ = function () {
    return parseInt(this.get('borderRadius'), 10) || 0;
};


/**
* borderRadius changed MVC callback
*/
InfoBubble.prototype.borderRadius_changed = function () {
    var borderRadius = this.getBorderRadius_();
    var borderWidth = this.getBorderWidth_();

    this.contentContainer_.style['borderRadius'] =
      this.contentContainer_.style['MozBorderRadius'] =
      this.contentContainer_.style['webkitBorderRadius'] =
      this.bubbleShadow_.style['borderRadius'] =
      this.bubbleShadow_.style['MozBorderRadius'] =
      this.bubbleShadow_.style['webkitBorderRadius'] = this.px(borderRadius);

    this.tabsContainer_.style['paddingLeft'] =
      this.tabsContainer_.style['paddingRight'] =
      this.px(borderRadius + borderWidth);

    this.redraw_();
};
InfoBubble.prototype['borderRadius_changed'] =
    InfoBubble.prototype.borderRadius_changed;


/**
* Get the width of the border
*
* @private
* @return {number} width The width of the border.
*/
InfoBubble.prototype.getBorderWidth_ = function () {
    return parseInt(this.get('borderWidth'), 10) || 0;
};


/**
* Set the width of the border
*
* @param {number} width The width of the border.
*/
InfoBubble.prototype.setBorderWidth = function (width) {
    this.set('borderWidth', width);
};
InfoBubble.prototype['setBorderWidth'] = InfoBubble.prototype.setBorderWidth;


/**
* borderWidth change MVC callback
*/
InfoBubble.prototype.borderWidth_changed = function () {
    var borderWidth = this.getBorderWidth_();

    this.contentContainer_.style['borderWidth'] = this.px(borderWidth);
    this.tabsContainer_.style['top'] = this.px(borderWidth);

    this.updateArrowStyle_();
    this.updateTabStyles_();
    this.borderRadius_changed();
    this.redraw_();
};
InfoBubble.prototype['borderWidth_changed'] =
    InfoBubble.prototype.borderWidth_changed;


/**
* Update the arrow style
* @private
*/
InfoBubble.prototype.updateArrowStyle_ = function () {
    var borderWidth = this.getBorderWidth_();
    var arrowSize = this.getArrowSize_();
    var arrowStyle = this.getArrowStyle_();
    var arrowOuterSizePx = this.px(arrowSize);
    var arrowInnerSizePx = this.px(arrowSize - borderWidth);

    var outer = this.arrowOuter_;
    var inner = this.arrowInner_;

    this.arrow_.style['marginTop'] = this.px(-borderWidth);
    outer.style['borderTopWidth'] = arrowOuterSizePx;
    inner.style['borderTopWidth'] = arrowInnerSizePx;

    // Full arrow or arrow pointing to the left
    if (arrowStyle == 0 || arrowStyle == 1) {
        outer.style['borderLeftWidth'] = arrowOuterSizePx;
        inner.style['borderLeftWidth'] = arrowInnerSizePx;
    } else {
        outer.style['borderLeftWidth'] = inner.style['borderLeftWidth'] = 0;
    }

    // Full arrow or arrow pointing to the right
    if (arrowStyle == 0 || arrowStyle == 2) {
        outer.style['borderRightWidth'] = arrowOuterSizePx;
        inner.style['borderRightWidth'] = arrowInnerSizePx;
    } else {
        outer.style['borderRightWidth'] = inner.style['borderRightWidth'] = 0;
    }

    if (arrowStyle < 2) {
        outer.style['marginLeft'] = this.px(-(arrowSize));
        inner.style['marginLeft'] = this.px(-(arrowSize - borderWidth));
    } else {
        outer.style['marginLeft'] = inner.style['marginLeft'] = 0;
    }

    // If there is no border then don't show thw outer arrow
    if (borderWidth == 0) {
        outer.style['display'] = 'none';
    } else {
        outer.style['display'] = '';
    }
};


/**
* Set the padding of the InfoBubble
*
* @param {number} padding The padding to apply.
*/
InfoBubble.prototype.setPadding = function (padding) {
    this.set('padding', padding);
};
InfoBubble.prototype['setPadding'] = InfoBubble.prototype.setPadding;


/**
* Set the padding of the InfoBubble
*
* @private
* @return {number} padding The padding to apply.
*/
InfoBubble.prototype.getPadding_ = function () {
    return parseInt(this.get('padding'), 10) || 0;
};


/**
* padding changed MVC callback
*/
InfoBubble.prototype.padding_changed = function () {
    var padding = this.getPadding_();
    this.contentContainer_.style['padding'] = this.px(padding);
    this.updateTabStyles_();

    this.redraw_();
};
InfoBubble.prototype['padding_changed'] = InfoBubble.prototype.padding_changed;


/**
* Add px extention to the number
*
* @param {number} num The number to wrap.
* @return {string|number} A wrapped number.
*/
InfoBubble.prototype.px = function (num) {
    if (num) {
        // 0 doesn't need to be wrapped
        return num + 'px';
    }
    return num;
};


/**
* Add events to stop propagation
* @private
*/
InfoBubble.prototype.addEvents_ = function () {
    // We want to cancel all the events so they do not go to the map
    var events = ['mousedown', 'mousemove', 'mouseover', 'mouseout', 'mouseup',
      'mousewheel', 'DOMMouseScroll', 'touchstart', 'touchend', 'touchmove',
      'dblclick', 'contextmenu'];

    var bubble = this.bubble_;
    this.listeners_ = [];
    for (var i = 0, event; event = events[i]; i++) {
        this.listeners_.push(
      google.maps.event.addDomListener(bubble, event, function (e) {
          e.cancelBubble = true;
          if (e.stopPropagation) {
              e.stopPropagation();
          }
      })
    );
    }
};


/**
* On Adding the InfoBubble to a map
* Implementing the OverlayView interface
*/
InfoBubble.prototype.onAdd = function () {
    if (!this.bubble_) {
        this.buildDom_();
    }

    this.addEvents_();

    var panes = this.getPanes();
    if (panes) {
        panes.floatPane.appendChild(this.bubble_);
        panes.floatShadow.appendChild(this.bubbleShadow_);
    }
};
InfoBubble.prototype['onAdd'] = InfoBubble.prototype.onAdd;


/**
* Draw the InfoBubble
* Implementing the OverlayView interface
*/
InfoBubble.prototype.draw = function () {
    var projection = this.getProjection();

    if (!projection) {
        // The map projection is not ready yet so do nothing
        return;
    }

    var latLng = /** @type {google.maps.LatLng} */(this.get('position'));

    if (!latLng) {
        this.close();
        return;
    }

    var tabHeight = 0;

    if (this.activeTab_) {
        tabHeight = this.activeTab_.offsetHeight;
    }

    var anchorHeight = this.getAnchorHeight_();
    var arrowSize = this.getArrowSize_();
    var arrowPosition = this.getArrowPosition_();

    arrowPosition = arrowPosition / 100;

    var pos = projection.fromLatLngToDivPixel(latLng);
    var width = this.contentContainer_.offsetWidth;
    var height = this.bubble_.offsetHeight;

    if (!width) {
        return;
    }

    // Adjust for the height of the info bubble
    var top = pos.y - (height + arrowSize);

    if (anchorHeight) {
        // If there is an anchor then include the height
        top -= anchorHeight;
    }

    var left = pos.x - (width * arrowPosition);

    this.bubble_.style['top'] = this.px(top);
    this.bubble_.style['left'] = this.px(left);

    var shadowStyle = parseInt(this.get('shadowStyle'), 10);

    switch (shadowStyle) {
        case 1:
            // Shadow is behind
            this.bubbleShadow_.style['top'] = this.px(top + tabHeight - 1);
            this.bubbleShadow_.style['left'] = this.px(left);
            this.bubbleShadow_.style['width'] = this.px(width);
            //this.bubbleShadow_.style['height'] =
          this.px(this.contentContainer_.offsetHeight - arrowSize);
            break;
        case 2:
            // Shadow is below
            width = width * 0.8;
            if (anchorHeight) {
                this.bubbleShadow_.style['top'] = this.px(pos.y);
            } else {
                this.bubbleShadow_.style['top'] = this.px(pos.y + arrowSize);
            }
            this.bubbleShadow_.style['left'] = this.px(pos.x - width * arrowPosition);

            this.bubbleShadow_.style['width'] = this.px(width);
            //this.bubbleShadow_.style['height'] = this.px(2);
            break;
    }
};
InfoBubble.prototype['draw'] = InfoBubble.prototype.draw;


/**
* Removing the InfoBubble from a map
*/
InfoBubble.prototype.onRemove = function () {
    if (this.bubble_ && this.bubble_.parentNode) {
        this.bubble_.parentNode.removeChild(this.bubble_);
    }
    if (this.bubbleShadow_ && this.bubbleShadow_.parentNode) {
        this.bubbleShadow_.parentNode.removeChild(this.bubbleShadow_);
    }

    for (var i = 0, listener; listener = this.listeners_[i]; i++) {
        google.maps.event.removeListener(listener);
    }
};
InfoBubble.prototype['onRemove'] = InfoBubble.prototype.onRemove;


/**
* Is the InfoBubble open
*
* @return {boolean} If the InfoBubble is open.
*/
InfoBubble.prototype.isOpen = function () {
    return this.isOpen_;
};
InfoBubble.prototype['isOpen'] = InfoBubble.prototype.isOpen;



/**
* Close the InfoBubble
*/
InfoBubble.prototype.close = function () {
    if (this.bubble_) {
        this.bubble_.style['display'] = 'none';
        // Remove the animation so we next time it opens it will animate again
        this.bubble_.className =
        this.bubble_.className.replace(this.animationName_, '');
    }

    if (this.bubbleShadow_) {
        this.bubbleShadow_.style['display'] = 'none';
        this.bubbleShadow_.className =
        this.bubbleShadow_.className.replace(this.animationName_, '');
    }
    this.isOpen_ = false;
};
InfoBubble.prototype['close'] = InfoBubble.prototype.close;


/**
* Open the InfoBubble
*
* @param {google.maps.Map=} opt_map Optional map to open on.
* @param {google.maps.MVCObject=} opt_anchor Optional anchor to position at.
*/
InfoBubble.prototype.open = function (opt_map, opt_anchor) {
    if (opt_map) {
        this.setMap(map);
    }

    if (opt_anchor) {
        this.set('anchor', opt_anchor);
        this.bindTo('position', opt_anchor);
    }

    // Show the bubble and the show
    this.bubble_.style['display'] = this.bubbleShadow_.style['display'] = '';
    var animation = !!!this.get('disableAnimation');

    if (animation) {
        // Add the animation
        this.bubble_.className += ' ' + this.animationName_;
        this.bubbleShadow_.className += ' ' + this.animationName_;
    }

    this.redraw_();
    this.isOpen_ = true;

    var pan = !!!this.get('disableAutoPan');
    if (pan) {
        var that = this;
        window.setTimeout(function () {
            // Pan into view, done in a time out to make it feel nicer :)
            that.panToView();
        }, 200);
    }
};
InfoBubble.prototype['open'] = InfoBubble.prototype.open;


/**
* Set the position of the InfoBubble
*
* @param {google.maps.LatLng} position The position to set.
*/
InfoBubble.prototype.setPosition = function (position) {
    if (position) {
        this.set('position', position);
    }
};
InfoBubble.prototype['setPosition'] = InfoBubble.prototype.setPosition;


/**
* Returns the position of the InfoBubble
*
* @return {google.maps.LatLng} the position.
*/
InfoBubble.prototype.getPosition = function () {
    return /** @type {google.maps.LatLng} */(this.get('position'));
};
InfoBubble.prototype['getPosition'] = InfoBubble.prototype.getPosition;


/**
* position changed MVC callback
*/
InfoBubble.prototype.position_changed = function () {
    this.draw();
};
InfoBubble.prototype['position_changed'] =
    InfoBubble.prototype.position_changed;


/**
* Pan the InfoBubble into view
*/
InfoBubble.prototype.panToView = function () {
    var projection = this.getProjection();

    if (!projection) {
        // The map projection is not ready yet so do nothing
        return;
    }

    if (!this.bubble_) {
        // No Bubble yet so do nothing
        return;
    }

    var anchorHeight = this.getAnchorHeight_();
    var height = this.bubble_.offsetHeight + anchorHeight;
    var map = this.get('map');
    var mapDiv = map.getDiv();
    var mapHeight = mapDiv.offsetHeight;

    var latLng = this.getPosition();
    var centerPos = projection.fromLatLngToContainerPixel(map.getCenter());
    var pos = projection.fromLatLngToContainerPixel(latLng);

    // Find out how much space at the top is free
    var spaceTop = centerPos.y - height;

    // Fine out how much space at the bottom is free
    var spaceBottom = mapHeight - centerPos.y;

    var needsTop = spaceTop < 0;
    var deltaY = 0;

    if (needsTop) {
        spaceTop *= -1;
        deltaY = (spaceTop + spaceBottom) / 2;
    }

    pos.y -= deltaY;
    latLng = projection.fromContainerPixelToLatLng(pos);

    if (map.getCenter() != latLng) {
        map.panTo(latLng);
    }
};
InfoBubble.prototype['panToView'] = InfoBubble.prototype.panToView;


/**
* Converts a HTML string to a document fragment.
*
* @param {string} htmlString The HTML string to convert.
* @return {Node} A HTML document fragment.
* @private
*/
InfoBubble.prototype.htmlToDocumentFragment_ = function (htmlString) {
    htmlString = htmlString.replace(/^\s*([\S\s]*)\b\s*$/, '$1');
    var tempDiv = document.createElement('DIV');
    tempDiv.innerHTML = htmlString;
    if (tempDiv.childNodes.length == 1) {
        return /** @type {!Node} */(tempDiv.removeChild(tempDiv.firstChild));
    } else {
        var fragment = document.createDocumentFragment();
        while (tempDiv.firstChild) {
            fragment.appendChild(tempDiv.firstChild);
        }
        return fragment;
    }
};


/**
* Removes all children from the node.
*
* @param {Node} node The node to remove all children from.
* @private
*/
InfoBubble.prototype.removeChildren_ = function (node) {
    if (!node) {
        return;
    }

    var child;
    while (child = node.firstChild) {
        node.removeChild(child);
    }
};


/**
* Sets the content of the infobubble.
*
* @param {string|Node} content The content to set.
*/
InfoBubble.prototype.setContent = function (content) {
    this.set('content', content);
};
InfoBubble.prototype['setContent'] = InfoBubble.prototype.setContent;


/**
* Get the content of the infobubble.
*
* @return {string|Node} The marker content.
*/
InfoBubble.prototype.getContent = function () {
    return /** @type {Node|string} */(this.get('content'));
};
InfoBubble.prototype['getContent'] = InfoBubble.prototype.getContent;


/**
* Sets the marker content and adds loading events to images
*/
InfoBubble.prototype.content_changed = function () {
    if (!this.content_) {
        // The Content area doesnt exist.
        return;
    }

    this.removeChildren_(this.content_);
    var content = this.getContent();
    if (content) {
        if (typeof content == 'string') {
            content = this.htmlToDocumentFragment_(content);
        }
        this.content_.appendChild(content);

        var that = this;
        var images = this.content_.getElementsByTagName('IMG');
        for (var i = 0, image; image = images[i]; i++) {
            // Because we don't know the size of an image till it loads, add a
            // listener to the image load so the marker can resize and reposition
            // itself to be the correct height.
            google.maps.event.addDomListener(image, 'load', function () {
                that.imageLoaded_();
            });
        }
        google.maps.event.trigger(this, 'domready');
    }
    this.redraw_();
};
InfoBubble.prototype['content_changed'] =
    InfoBubble.prototype.content_changed;


/**
* Image loaded
* @private
*/
InfoBubble.prototype.imageLoaded_ = function () {
    var pan = !!!this.get('disableAutoPan');
    this.redraw_();
    if (pan && (this.tabs_.length == 0 || this.activeTab_.index == 0)) {
        this.panToView();
    }
};

/**
* Updates the styles of the tabs
* @private
*/
InfoBubble.prototype.updateTabStyles_ = function () {
    if (this.tabs_ && this.tabs_.length) {
        for (var i = 0, tab; tab = this.tabs_[i]; i++) {
            this.setTabStyle_(tab.tab);
        }
        this.activeTab_.style['zIndex'] = this.baseZIndex_;
        var borderWidth = this.getBorderWidth_();
        var padding = this.getPadding_() / 2;
        this.activeTab_.style['borderBottomWidth'] = 0;
        this.activeTab_.style['paddingBottom'] = this.px(padding + borderWidth);
    }
};


/**
* Sets the style of a tab
* @private
* @param {Element} tab The tab to style.
*/
InfoBubble.prototype.setTabStyle_ = function (tab) {
    var backgroundColor = this.get('backgroundColor');
    var borderColor = this.get('borderColor');
    var borderRadius = this.getBorderRadius_();
    var borderWidth = this.getBorderWidth_();
    var padding = this.getPadding_();

    var marginRight = this.px(-(Math.max(padding, borderRadius)));
    var borderRadiusPx = this.px(borderRadius);

    var index = this.baseZIndex_;
    if (tab.index) {
        index -= tab.index;
    }

    // The styles for the tab
    var styles = {
        'cssFloat': 'left',
        'position': 'relative',
        'cursor': 'pointer',
        'backgroundColor': backgroundColor,
        'border': this.px(borderWidth) + ' solid ' + borderColor,
        'padding': this.px(padding / 2) + ' ' + this.px(padding),
        'marginRight': marginRight,
        'whiteSpace': 'nowrap',
        'borderRadiusTopLeft': borderRadiusPx,
        'MozBorderRadiusTopleft': borderRadiusPx,
        'webkitBorderTopLeftRadius': borderRadiusPx,
        'borderRadiusTopRight': borderRadiusPx,
        'MozBorderRadiusTopright': borderRadiusPx,
        'webkitBorderTopRightRadius': borderRadiusPx,
        'zIndex': index
    };

    for (var style in styles) {
        tab.style[style] = styles[style];
    }

    var className = this.get('tabClassName');
    if (className != undefined) {
        tab.className += ' ' + className;
    }
};


/**
* Add user actions to a tab
* @private
* @param {Object} tab The tab to add the actions to.
*/
InfoBubble.prototype.addTabActions_ = function (tab) {
    var that = this;
    tab.listener_ = google.maps.event.addDomListener(tab, 'click', function () {
        that.setTabActive_(this);
    });
};


/**
* Set a tab at a index to be active
*
* @param {number} index The index of the tab.
*/
InfoBubble.prototype.setTabActive = function (index) {
    var tab = this.tabs_[index - 1];

    if (tab) {
        this.setTabActive_(tab.tab);
    }
};
InfoBubble.prototype['setTabActive'] = InfoBubble.prototype.setTabActive;


/**
* Set a tab to be active
* @private
* @param {Object} tab The tab to set active.
*/
InfoBubble.prototype.setTabActive_ = function (tab) {
    if (!tab) {
        this.setContent('');
        return;
    }

    var padding = this.getPadding_() / 2;
    var borderWidth = this.getBorderWidth_();

    if (this.activeTab_) {
        var activeTab = this.activeTab_;
        activeTab.style['zIndex'] = this.baseZIndex_ - activeTab.index;
        activeTab.style['paddingBottom'] = this.px(padding);
        activeTab.style['borderBottomWidth'] = this.px(borderWidth);
    }

    tab.style['zIndex'] = this.baseZIndex_;
    tab.style['borderBottomWidth'] = 0;
    tab.style['paddingBottom'] = this.px(padding + borderWidth);

    this.setContent(this.tabs_[tab.index].content);

    this.activeTab_ = tab;

    this.redraw_();
};


/**
* Set the max width of the InfoBubble
*
* @param {number} width The max width.
*/
InfoBubble.prototype.setMaxWidth = function (width) {
    this.set('maxWidth', width);
};
InfoBubble.prototype['setMaxWidth'] = InfoBubble.prototype.setMaxWidth;


/**
* maxWidth changed MVC callback
*/
InfoBubble.prototype.maxWidth_changed = function () {
    this.redraw_();
};
InfoBubble.prototype['maxWidth_changed'] =
    InfoBubble.prototype.maxWidth_changed;


/**
* Set the max height of the InfoBubble
*
* @param {number} height The max height.
*/
InfoBubble.prototype.setMaxHeight = function (height) {
    this.set('maxHeight', height);
};
InfoBubble.prototype['setMaxHeight'] = InfoBubble.prototype.setMaxHeight;


/**
* maxHeight changed MVC callback
*/
InfoBubble.prototype.maxHeight_changed = function () {
    this.redraw_();
};
InfoBubble.prototype['maxHeight_changed'] =
    InfoBubble.prototype.maxHeight_changed;


/**
* Set the min width of the InfoBubble
*
* @param {number} width The min width.
*/
InfoBubble.prototype.setMinWidth = function (width) {
    this.set('minWidth', width);
};
InfoBubble.prototype['setMinWidth'] = InfoBubble.prototype.setMinWidth;


/**
* minWidth changed MVC callback
*/
InfoBubble.prototype.minWidth_changed = function () {
    this.redraw_();
};
InfoBubble.prototype['minWidth_changed'] =
    InfoBubble.prototype.minWidth_changed;


/**
* Set the min height of the InfoBubble
*
* @param {number} height The min height.
*/
InfoBubble.prototype.setMinHeight = function (height) {
    this.set('minHeight', height);
};
InfoBubble.prototype['setMinHeight'] = InfoBubble.prototype.setMinHeight;


/**
* minHeight changed MVC callback
*/
InfoBubble.prototype.minHeight_changed = function () {
    this.redraw_();
};
InfoBubble.prototype['minHeight_changed'] =
    InfoBubble.prototype.minHeight_changed;


/**
* Add a tab
*
* @param {string} label The label of the tab.
* @param {string|Element} content The content of the tab.
*/
InfoBubble.prototype.addTab = function (label, content) {
    var tab = document.createElement('DIV');
    tab.innerHTML = label;

    this.setTabStyle_(tab);
    this.addTabActions_(tab);

    this.tabsContainer_.appendChild(tab);

    this.tabs_.push({
        label: label,
        content: content,
        tab: tab
    });

    tab.index = this.tabs_.length - 1;
    tab.style['zIndex'] = this.baseZIndex_ - tab.index;

    if (!this.activeTab_) {
        this.setTabActive_(tab);
    }

    tab.className = tab.className + ' ' + this.animationName_;

    this.redraw_();
};
InfoBubble.prototype['addTab'] = InfoBubble.prototype.addTab;


/**
* Remove a tab at a specific index
*
* @param {number} index The index of the tab to remove.
*/
InfoBubble.prototype.removeTab = function (index) {
    if (!this.tabs_.length || index < 0 || index >= this.tabs_.length) {
        return;
    }

    var tab = this.tabs_[index];
    tab.tab.parentNode.removeChild(tab.tab);

    google.maps.event.removeListener(tab.tab.listener_);

    this.tabs_.splice(index, 1);

    delete tab;

    for (var i = 0, t; t = this.tabs_[i]; i++) {
        t.tab.index = i;
    }

    if (tab.tab == this.activeTab_) {
        // Removing the current active tab
        if (this.tabs_[index]) {
            // Show the tab to the right
            this.activeTab_ = this.tabs_[index].tab;
        } else if (this.tabs_[index - 1]) {
            // Show a tab to the left
            this.activeTab_ = this.tabs_[index - 1].tab;
        } else {
            // No tabs left to sho
            this.activeTab_ = undefined;
        }

        this.setTabActive_(this.activeTab_);
    }

    this.redraw_();
};
InfoBubble.prototype['removeTab'] = InfoBubble.prototype.removeTab;


/**
* Get the size of an element
* @private
* @param {Node|string} element The element to size.
* @param {number=} opt_maxWidth Optional max width of the element.
* @param {number=} opt_maxHeight Optional max height of the element.
* @return {google.maps.Size} The size of the element.
*/
InfoBubble.prototype.getElementSize_ = function (element, opt_maxWidth,
                                                opt_maxHeight) {
    var sizer = document.createElement('DIV');
    sizer.style['display'] = 'inline';
    sizer.style['position'] = 'absolute';
    sizer.style['visibility'] = 'hidden';

    if (typeof element == 'string') {
        sizer.innerHTML = element;
    } else {
        sizer.appendChild(element.cloneNode(true));
    }

    document.body.appendChild(sizer);
    var size = new google.maps.Size(sizer.offsetWidth, sizer.offsetHeight);

    // If the width is bigger than the max width then set the width and size again
    if (opt_maxWidth && size.width > opt_maxWidth) {
        sizer.style['width'] = this.px(opt_maxWidth);
        size = new google.maps.Size(sizer.offsetWidth, sizer.offsetHeight);
    }

    // If the height is bigger than the max height then set the height and size
    // again
    if (opt_maxHeight && size.height > opt_maxHeight) {
        //sizer.style['height'] = this.px(opt_maxHeight);
        //size = new google.maps.Size(sizer.offsetWidth, sizer.offsetHeight);
    }

    document.body.removeChild(sizer);
    delete sizer;
    return size;
};


/**
* Redraw the InfoBubble
* @private
*/
InfoBubble.prototype.redraw_ = function () {
    this.figureOutSize_();
    this.positionCloseButton_();
    this.draw();
};


/**
* Figure out the optimum size of the InfoBubble
* @private
*/
InfoBubble.prototype.figureOutSize_ = function () {
    var map = this.get('map');

    if (!map) {
        return;
    }

    var padding = this.getPadding_();
    var borderWidth = this.getBorderWidth_();
    var borderRadius = this.getBorderRadius_();
    var arrowSize = this.getArrowSize_();

    var mapDiv = map.getDiv();
    var gutter = arrowSize * 2;
    var mapWidth = mapDiv.offsetWidth - gutter;
    var mapHeight = mapDiv.offsetHeight - gutter - this.getAnchorHeight_();
    var tabHeight = 0;
    var width = /** @type {number} */(this.get('minWidth') || 0);
    var height = /** @type {number} */(this.get('minHeight') || 0);
    var maxWidth = /** @type {number} */(this.get('maxWidth') || 0);
    var maxHeight = /** @type {number} */(this.get('maxHeight') || 0);

    maxWidth = Math.min(mapWidth, maxWidth);
    maxHeight = Math.min(mapHeight, maxHeight);

    var tabWidth = 0;
    if (this.tabs_.length) {
        // If there are tabs then you need to check the size of each tab's content
        for (var i = 0, tab; tab = this.tabs_[i]; i++) {
            var tabSize = this.getElementSize_(tab.tab, maxWidth, maxHeight);
            var contentSize = this.getElementSize_(tab.content, maxWidth, maxHeight);

            if (width < tabSize.width) {
                width = tabSize.width;
            }

            // Add up all the tab widths because they might end up being wider than
            // the content
            tabWidth += tabSize.width;

            if (height < tabSize.height) {
                height = tabSize.height;
            }

            if (tabSize.height > tabHeight) {
                tabHeight = tabSize.height;
            }

            if (width < contentSize.width) {
                width = contentSize.width;
            }

            if (height < contentSize.height) {
                height = contentSize.height;
            }
        }
    } else {
        var content = /** @type {string|Node} */(this.get('content'));
        if (typeof content == 'string') {
            content = this.htmlToDocumentFragment_(content);
        }
        if (content) {
            var contentSize = this.getElementSize_(content, maxWidth, maxHeight);

            if (width < contentSize.width) {
                width = contentSize.width;
            }

            if (height < contentSize.height) {
                height = contentSize.height;
            }
        }
    }

    if (maxWidth) {
        width = Math.min(width, maxWidth);
    }

    if (maxHeight) {
        height = Math.min(height, maxHeight);
    }

    width = Math.max(width, tabWidth);

    if (width == tabWidth) {
        width = width + 2 * padding;
    }

    arrowSize = arrowSize * 2;
    width = Math.max(width, arrowSize);

    // Maybe add this as a option so they can go bigger than the map if the user
    // wants
    if (width > mapWidth) {
        width = mapWidth;
    }

    if (height > mapHeight) {
        height = mapHeight - tabHeight;
    }

    if (this.tabsContainer_) {
        this.tabHeight_ = tabHeight;
        this.tabsContainer_.style['width'] = this.px(tabWidth);
    }

    this.contentContainer_.style['width'] = this.px(width);
    //this.contentContainer_.style['height'] = this.px(height);
};


/**
*  Get the height of the anchor
*
*  This function is a hack for now and doesn't really work that good, need to
*  wait for pixelBounds to be correctly exposed.
*  @private
*  @return {number} The height of the anchor.
*/
InfoBubble.prototype.getAnchorHeight_ = function () {
    var anchorHeight = 0;
    var anchor = this.get('anchor');
    if (anchor) {

        if (!anchorHeight && anchor.height) {
            anchorHeight = anchor.height;
        }

        // HACK
        if (!anchorHeight) {
            anchorHeight = 24;
        }
    }
    return anchorHeight;
};


/**
* Position the close button in the right spot.
* @private
*/
InfoBubble.prototype.positionCloseButton_ = function () {
    var br = this.getBorderRadius_();
    var bw = this.getBorderWidth_();

    var right = 2;
    var top = 2;

    if (this.tabs_.length && this.tabHeight_) {
        top += this.tabHeight_;
    }

    top += bw;
    right += bw;

    var c = this.contentContainer_;
    if (c && c.clientHeight < c.scrollHeight) {
        // If there are scrollbars then move the cross in so it is not over
        // scrollbar
        right += 15;
    }

    this.close_.style['right'] = this.px(right);
    this.close_.style['top'] = this.px(top);
};;
var InfoBubbleManager = (function () {
    function InfoBubbleManager(map, templateDelegate) {
        this.map = map;
        this.selectedBubble = null;
        this.templateDelegate = templateDelegate;
    }
    InfoBubbleManager.prototype.showBubble = function (marker, event) {
        var infoBubble = new InfoBubble({
            map: this.map,
            content: this.templateDelegate(marker.item),
            //position: new google.maps.LatLng(-35, 151),
            shadowStyle: 0,
            padding: 0,
            backgroundColor: '#fff',
            borderRadius: 0,
            arrowSize: 0,
            borderWidth: 0,
            //borderColor: '#fff',
            disableAutoPan: false,
            hideCloseButton: true,
            arrowPosition: 50,
            backgroundClassName: 'phoney',
            arrowStyle: 0,
            minWidth: 0
            
        });

        if (this.selectedBubble !== infoBubble) {
            this.closeBubble();
        }
        if (!infoBubble.isOpen()) {
            this.selectedBubble = infoBubble;
            infoBubble.open(undefined, marker);
        }

        $(infoBubble.bubble_).on('click', function (e) {
            e.cancelBubble = true;
            if (e.stopPropagation) {
                e.stopPropagation();
            }
        }); 
    };

    InfoBubbleManager.prototype.closeBubble = function () {
        if (this.selectedBubble && this.selectedBubble.isOpen()) {
            this.selectedBubble.close();
        }
        this.selectedBubble = null;
    };
    return InfoBubbleManager;
})();
;
/*jslint browser: true, confusion: true, sloppy: true, vars: true, nomen: false, plusplus: false, indent: 2 */
/*global window,google */

/**
 * @name MarkerClustererPlus for Google Maps V3
 * @version 2.0.9 [February 20, 2012]
 * @author Gary Little
 * @fileoverview
 * The library creates and manages per-zoom-level clusters for large amounts of markers.
 * <p>
 * This is an enhanced V3 implementation of the
 * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
 * >V2 MarkerClusterer</a> by Xiaoxi Wu. It is based on the
 * <a href="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/"
 * >V3 MarkerClusterer</a> port by Luke Mahe. MarkerClustererPlus was created by Gary Little.
 * <p>
 * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It
 *  adds support for the <code>ignoreHidden</code>, <code>title</code>, <code>printable</code>,
 *  <code>batchSizeIE</code>, and <code>calculator</code> properties as well as support for
 *  four more events. It also allows greater control over the styling of the text that appears
 *  on the cluster marker. The documentation has been significantly improved and the overall
 *  code has been simplified and polished. Very large numbers of markers can now be managed
 *  without causing Javascript timeout errors on Internet Explorer. Note that the name of the
 *  <code>clusterclick</code> event has been deprecated. The new name is <code>click</code>,
 *  so please change your application code now.
 */

/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/**
 * @name ClusterIconStyle
 * @class This class represents the object for values in the <code>styles</code> array passed
 *  to the {@link MarkerClusterer} constructor. The element in this array that is used to
 *  style the cluster icon is determined by calling the <code>calculator</code> function.
 *
 * @property {string} url The URL of the cluster icon image file. Required.
 * @property {number} height The height (in pixels) of the cluster icon. Required.
 * @property {number} width The width (in pixels) of the cluster icon. Required.
 * @property {Array} [anchor] The anchor position (in pixels) of the label text to be shown on
 *  the cluster icon, relative to the top left corner of the icon.
 *  The format is <code>[yoffset, xoffset]</code>. The <code>yoffset</code> must be positive
 *  and less than <code>height</code> and the <code>xoffset</code> must be positive and less
 *  than <code>width</code>. The default is to anchor the label text so that it is centered
 *  on the icon.
 * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the
 *  spot on the cluster icon that is to be aligned with the cluster position. The format is
 *  <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and
 *  <code>xoffset</code> increases to the right. The default anchor position is the center of the
 *  cluster icon.
 * @property {string} [textColor="black"] The color of the label text shown on the
 *  cluster icon.
 * @property {number} [textSize=11] The size (in pixels) of the label text shown on the
 *  cluster icon.
 * @property {number} [textDecoration="none"] The value of the CSS <code>text-decoration</code>
 *  property for the label text shown on the cluster icon.
 * @property {number} [fontWeight="bold"] The value of the CSS <code>font-weight</code>
 *  property for the label text shown on the cluster icon.
 * @property {number} [fontStyle="normal"] The value of the CSS <code>font-style</code>
 *  property for the label text shown on the cluster icon.
 * @property {number} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code>
 *  property for the label text shown on the cluster icon.
 * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image
 *  within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code>
 *  (the same format as for the CSS <code>background-position</code> property). You must set
 *  this property appropriately when the image defined by <code>url</code> represents a sprite
 *  containing multiple images.
 */
/**
 * @name ClusterIconInfo
 * @class This class is an object containing general information about a cluster icon. This is
 *  the object that a <code>calculator</code> function returns.
 *
 * @property {string} text The text of the label to be shown on the cluster icon.
 * @property {number} index The index plus 1 of the element in the <code>styles</code>
 *  array to be used to style the cluster icon.
 */
/**
 * A cluster icon.
 *
 * @constructor
 * @extends google.maps.OverlayView
 * @param {Cluster} cluster The cluster with which the icon is to be associated.
 * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons
 *  to use for various cluster sizes.
 * @private
 */
function ClusterIcon(cluster, styles) {
    cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);

    this.cluster_ = cluster;
    this.styles_ = styles;
    this.center_ = null;
    this.div_ = null;
    this.sums_ = null;
    this.visible_ = false;

    this.setMap(cluster.getMap()); // Note: this causes onAdd to be called
}


/**
 * Adds the icon to the DOM.
 */
ClusterIcon.prototype.onAdd = function () {
    var cClusterIcon = this;
    var cMouseDownInCluster;
    var cDraggingMapByCluster;

    this.div_ = document.createElement("div");
    if (this.visible_) {
        this.show();
    }

    this.getPanes().overlayMouseTarget.appendChild(this.div_);

    // Fix for Issue 157
    google.maps.event.addListener(this.getMap(), "bounds_changed", function () {
        cDraggingMapByCluster = cMouseDownInCluster;
    });

    google.maps.event.addDomListener(this.div_, "mousedown", function () {
        cMouseDownInCluster = true;
        cDraggingMapByCluster = false;
    });

    google.maps.event.addDomListener(this.div_, "click", function (e) {
        cMouseDownInCluster = false;
        if (!cDraggingMapByCluster) {
            var mz;
            var mc = cClusterIcon.cluster_.getMarkerClusterer();
            /**
             * This event is fired when a cluster marker is clicked.
             * @name MarkerClusterer#click
             * @param {Cluster} c The cluster that was clicked.
             * @event
             */
            google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
            google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name

            // The default click handler follows. Disable it by setting
            // the zoomOnClick property to false.
            if (mc.getZoomOnClick()) {
                // Zoom into the cluster.
                mz = mc.getMaxZoom();
                mc.getMap().fitBounds(cClusterIcon.cluster_.getBounds());
                // Don't zoom beyond the max zoom level
                if (mz !== null && (mc.getMap().getZoom() > mz)) {
                    mc.getMap().setZoom(mz + 1);
                }
            }

            // Prevent event propagation to the map:
            e.cancelBubble = true;
            if (e.stopPropagation) {
                e.stopPropagation();
            }
        }
    });

    google.maps.event.addDomListener(this.div_, "mouseover", function () {
        var mc = cClusterIcon.cluster_.getMarkerClusterer();
        /**
         * This event is fired when the mouse moves over a cluster marker.
         * @name MarkerClusterer#mouseover
         * @param {Cluster} c The cluster that the mouse moved over.
         * @event
         */
        google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_);
    });

    google.maps.event.addDomListener(this.div_, "mouseout", function () {
        var mc = cClusterIcon.cluster_.getMarkerClusterer();
        /**
         * This event is fired when the mouse moves out of a cluster marker.
         * @name MarkerClusterer#mouseout
         * @param {Cluster} c The cluster that the mouse moved out of.
         * @event
         */
        google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_);
    });
};


/**
 * Removes the icon from the DOM.
 */
ClusterIcon.prototype.onRemove = function () {
    if (this.div_ && this.div_.parentNode) {
        this.hide();
        google.maps.event.clearInstanceListeners(this.div_);
        this.div_.parentNode.removeChild(this.div_);
        this.div_ = null;
    }
};


/**
 * Draws the icon.
 */
ClusterIcon.prototype.draw = function () {
    if (this.visible_) {
        var pos = this.getPosFromLatLng_(this.center_);
        this.div_.style.top = pos.y + "px";
        this.div_.style.left = pos.x + "px";
    }
};


/**
 * Hides the icon.
 */
ClusterIcon.prototype.hide = function () {
    if (this.div_) {
        this.div_.style.display = "none";
    }
    this.visible_ = false;
};


/**
 * Positions and shows the icon.
 */
ClusterIcon.prototype.show = function () {
    if (this.div_) {
        var pos = this.getPosFromLatLng_(this.center_);
        this.div_.style.cssText = this.createCss(pos);
        if (this.cluster_.printable_) {
            // (Would like to use "width: inherit;" below, but doesn't work with MSIE)
            this.div_.innerHTML = "<img src='" + this.url_ + "'><div style='position: absolute; top: 0px; left: 0px; width: " + this.width_ + "px;'>" + this.sums_.text + "</div>";
        } else {
            this.div_.innerHTML = this.sums_.text;
        }
        this.div_.title = this.cluster_.getMarkerClusterer().getTitle();
        this.div_.style.display = "";
    }
    this.visible_ = true;
};


/**
 * Sets the icon styles to the appropriate element in the styles array.
 *
 * @param {ClusterIconInfo} sums The icon label text and styles index.
 */
ClusterIcon.prototype.useStyle = function (sums) {
    this.sums_ = sums;
    var index = Math.max(0, sums.index - 1);
    index = Math.min(this.styles_.length - 1, index);
    var style = this.styles_[index];
    this.url_ = style.url;
    this.height_ = style.height;
    this.width_ = style.width;
    this.anchor_ = style.anchor;
    this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)];
    this.textColor_ = style.textColor || "black";
    this.textSize_ = style.textSize || 11;
    this.textDecoration_ = style.textDecoration || "none";
    this.fontWeight_ = style.fontWeight || "bold";
    this.fontStyle_ = style.fontStyle || "normal";
    this.fontFamily_ = style.fontFamily || "Arial,sans-serif";
    this.backgroundPosition_ = style.backgroundPosition || "0 0";
};


/**
 * Sets the position at which to center the icon.
 *
 * @param {google.maps.LatLng} center The latlng to set as the center.
 */
ClusterIcon.prototype.setCenter = function (center) {
    this.center_ = center;
};


/**
 * Creates the cssText style parameter based on the position of the icon.
 *
 * @param {google.maps.Point} pos The position of the icon.
 * @return {string} The CSS style text.
 */
ClusterIcon.prototype.createCss = function (pos) {
    var style = [];
    if (!this.cluster_.printable_) {
        style.push('background-image:url(' + this.url_ + ');');
        style.push('background-position:' + this.backgroundPosition_ + ';');
    }

    if (typeof this.anchor_ === 'object') {
        if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 &&
            this.anchor_[0] < this.height_) {
            style.push('height:' + (this.height_ - this.anchor_[0]) +
                'px; padding-top:' + this.anchor_[0] + 'px;');
        } else {
            style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
                'px;');
        }
        if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 &&
            this.anchor_[1] < this.width_) {
            style.push('width:' + (this.width_ - this.anchor_[1]) +
                'px; padding-left:' + this.anchor_[1] + 'px;');
        } else {
            style.push('width:' + this.width_ + 'px; text-align:center;');
        }
    } else {
        style.push('height:' + this.height_ + 'px; line-height:' +
            this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
    }

    style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
        pos.x + 'px; color:' + this.textColor_ + '; position:absolute; font-size:' +
        this.textSize_ + 'px; font-family:' + this.fontFamily_ + '; font-weight:' +
        this.fontWeight_ + '; font-style:' + this.fontStyle_ + '; text-decoration:' +
        this.textDecoration_ + ';');

    return style.join("");
};


/**
 * Returns the position at which to place the DIV depending on the latlng.
 *
 * @param {google.maps.LatLng} latlng The position in latlng.
 * @return {google.maps.Point} The position in pixels.
 */
ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) {
    var pos = this.getProjection().fromLatLngToDivPixel(latlng);
    pos.x -= this.anchorIcon_[1];
    pos.y -= this.anchorIcon_[0];
    return pos;
};


/**
 * Creates a single cluster that manages a group of proximate markers.
 *  Used internally, do not call this constructor directly.
 * @constructor
 * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this
 *  cluster is associated.
 */
function Cluster(mc) {
    this.markerClusterer_ = mc;
    this.map_ = mc.getMap();
    this.gridSize_ = mc.getGridSize();
    this.minClusterSize_ = mc.getMinimumClusterSize();
    this.averageCenter_ = mc.getAverageCenter();
    this.printable_ = mc.getPrintable();
    this.markers_ = [];
    this.center_ = null;
    this.bounds_ = null;
    this.clusterIcon_ = new ClusterIcon(this, mc.getStyles());
}


/**
 * Returns the number of markers managed by the cluster. You can call this from
 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
 * for the <code>MarkerClusterer</code> object.
 *
 * @return {number} The number of markers in the cluster.
 */
Cluster.prototype.getSize = function () {
    return this.markers_.length;
};


/**
 * Returns the array of markers managed by the cluster. You can call this from
 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
 * for the <code>MarkerClusterer</code> object.
 *
 * @return {Array} The array of markers in the cluster.
 */
Cluster.prototype.getMarkers = function () {
    return this.markers_;
};


/**
 * Returns the center of the cluster. You can call this from
 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
 * for the <code>MarkerClusterer</code> object.
 *
 * @return {google.maps.LatLng} The center of the cluster.
 */
Cluster.prototype.getCenter = function () {
    return this.center_;
};


/**
 * Returns the map with which the cluster is associated.
 *
 * @return {google.maps.Map} The map.
 * @ignore
 */
Cluster.prototype.getMap = function () {
    return this.map_;
};


/**
 * Returns the <code>MarkerClusterer</code> object with which the cluster is associated.
 *
 * @return {MarkerClusterer} The associated marker clusterer.
 * @ignore
 */
Cluster.prototype.getMarkerClusterer = function () {
    return this.markerClusterer_;
};


/**
 * Returns the bounds of the cluster.
 *
 * @return {google.maps.LatLngBounds} the cluster bounds.
 * @ignore
 */
Cluster.prototype.getBounds = function () {
    var i;
    var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
    var markers = this.getMarkers();
    for (i = 0; i < markers.length; i++) {
        bounds.extend(markers[i].getPosition());
    }
    return bounds;
};


/**
 * Removes the cluster from the map.
 *
 * @ignore
 */
Cluster.prototype.remove = function () {
    this.clusterIcon_.setMap(null);
    this.markers_ = [];
    delete this.markers_;
};


/**
 * Adds a marker to the cluster.
 *
 * @param {google.maps.Marker} marker The marker to be added.
 * @return {boolean} True if the marker was added.
 * @ignore
 */
Cluster.prototype.addMarker = function (marker) {
    var i;
    var mCount;
    var mz;

    if (this.isMarkerAlreadyAdded_(marker)) {
        return false;
    }

    if (!this.center_) {
        this.center_ = marker.getPosition();
        this.calculateBounds_();
    } else {
        if (this.averageCenter_) {
            var l = this.markers_.length + 1;
            var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l;
            var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l;
            this.center_ = new google.maps.LatLng(lat, lng);
            this.calculateBounds_();
        }
    }

    marker.isAdded = true;
    this.markers_.push(marker);

    mCount = this.markers_.length;
    mz = this.markerClusterer_.getMaxZoom();
    if (mz !== null && this.map_.getZoom() > mz) {
        // Zoomed in past max zoom, so show the marker.
        if (marker.getMap() !== this.map_) {
            marker.setMap(this.map_);
        }
    } else if (mCount < this.minClusterSize_) {
        // Min cluster size not reached so show the marker.
        if (marker.getMap() !== this.map_) {
            marker.setMap(this.map_);
        }
    } else if (mCount === this.minClusterSize_) {
        // Hide the markers that were showing.
        for (i = 0; i < mCount; i++) {
            this.markers_[i].setMap(null);
        }
    } else {
        marker.setMap(null);
    }

    this.updateIcon_();
    return true;
};


/**
 * Determines if a marker lies within the cluster's bounds.
 *
 * @param {google.maps.Marker} marker The marker to check.
 * @return {boolean} True if the marker lies in the bounds.
 * @ignore
 */
Cluster.prototype.isMarkerInClusterBounds = function (marker) {
    return this.bounds_.contains(marker.getPosition());
};


/**
 * Calculates the extended bounds of the cluster with the grid.
 */
Cluster.prototype.calculateBounds_ = function () {
    var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
    this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
};


/**
 * Updates the cluster icon.
 */
Cluster.prototype.updateIcon_ = function () {
    var mCount = this.markers_.length;
    var mz = this.markerClusterer_.getMaxZoom();

    if (mz !== null && this.map_.getZoom() > mz) {
        this.clusterIcon_.hide();
        return;
    }

    if (mCount < this.minClusterSize_) {
        // Min cluster size not yet reached.
        this.clusterIcon_.hide();
        return;
    }

    var numStyles = this.markerClusterer_.getStyles().length;
    var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
    this.clusterIcon_.setCenter(this.center_);
    this.clusterIcon_.useStyle(sums);
    this.clusterIcon_.show();
};


/**
 * Determines if a marker has already been added to the cluster.
 *
 * @param {google.maps.Marker} marker The marker to check.
 * @return {boolean} True if the marker has already been added.
 */
Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) {
    var i;
    if (this.markers_.indexOf) {
        return this.markers_.indexOf(marker) !== -1;
    } else {
        for (i = 0; i < this.markers_.length; i++) {
            if (marker === this.markers_[i]) {
                return true;
            }
        }
    }
    return false;
};


/**
 * @name MarkerClustererOptions
 * @class This class represents the optional parameter passed to
 *  the {@link MarkerClusterer} constructor.
 * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square.
 * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or
 *  <code>null</code> if clustering is to be enabled at all zoom levels.
 * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is
 *  clicked. You may want to set this to <code>false</code> if you have installed a handler
 *  for the <code>click</code> event and it deals with zooming on its own.
 * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be
 *  the average position of all markers in the cluster. If set to <code>false</code>, the
 *  cluster marker is positioned at the location of the first marker added to the cluster.
 * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster
 *  before the markers are hidden and a cluster marker appears.
 * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You
 *  may want to set this to <code>true</code> to ensure that hidden markers are not included
 *  in the marker count that appears on a cluster marker (this count is the value of the
 *  <code>text</code> property of the result returned by the default <code>calculator</code>).
 *  If set to <code>true</code> and you change the visibility of a marker being clustered, be
 *  sure to also call <code>MarkerClusterer.repaint()</code>.
 * @property {boolean} [printable=false] Whether to make the cluster icons printable. Do not
 *  set to <code>true</code> if the <code>url</code> fields in the <code>styles</code> array
 *  refer to image sprite files.
 * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster
 *  marker.
 * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine
 *  the text to be displayed on a cluster marker and the index indicating which style to use
 *  for the cluster marker. The input parameters for the function are (1) the array of markers
 *  represented by a cluster marker and (2) the number of cluster icon styles. It returns a
 *  {@link ClusterIconInfo} object. The default <code>calculator</code> returns a
 *  <code>text</code> property which is the number of markers in the cluster and an
 *  <code>index</code> property which is one higher than the lowest integer such that
 *  <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles
 *  array, whichever is less. The <code>styles</code> array element used has an index of
 *  <code>index</code> minus 1. For example, the default <code>calculator</code> returns a
 *  <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code>
 *  for a cluster icon representing 125 markers so the element used in the <code>styles</code>
 *  array is <code>2</code>.
 * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles
 *  of the cluster markers to be used. The element to be used to style a given cluster marker
 *  is determined by the function defined by the <code>calculator</code> property.
 *  The default is an array of {@link ClusterIconStyle} elements whose properties are derived
 *  from the values for <code>imagePath</code>, <code>imageExtension</code>, and
 *  <code>imageSizes</code>.
 * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the
 *  number of markers to be processed in a single batch when using a browser other than
 *  Internet Explorer (for Internet Explorer, use the batchSizeIE property instead).
 * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is
 *  being used, markers are processed in several batches with a small delay inserted between
 *  each batch in an attempt to avoid Javascript timeout errors. Set this property to the
 *  number of markers to be processed in a single batch; select as high a number as you can
 *  without causing a timeout error in the browser. This number might need to be as low as 100
 *  if 15,000 markers are being managed, for example.
 * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH]
 *  The full URL of the root name of the group of image files to use for cluster icons.
 *  The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code>
 *  where n is the image file number (1, 2, etc.).
 * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION]
 *  The extension name for the cluster icon image files (e.g., <code>"png"</code> or
 *  <code>"jpg"</code>).
 * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES]
 *  An array of numbers containing the widths of the group of
 *  <code>imagePath</code>n.<code>imageExtension</code> image files.
 *  (The images are assumed to be square.)
 */
/**
 * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}.
 * @constructor
 * @extends google.maps.OverlayView
 * @param {google.maps.Map} map The Google map to attach to.
 * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster.
 * @param {MarkerClustererOptions} [opt_options] The optional parameters.
 */
function MarkerClusterer(map, opt_markers, opt_options) {
    // MarkerClusterer implements google.maps.OverlayView interface. We use the
    // extend function to extend MarkerClusterer with google.maps.OverlayView
    // because it might not always be available when the code is defined so we
    // look for it at the last possible moment. If it doesn't exist now then
    // there is no point going ahead :)
    this.extend(MarkerClusterer, google.maps.OverlayView);

    opt_markers = opt_markers || [];
    opt_options = opt_options || {};

    this.markers_ = [];
    this.clusters_ = [];
    this.listeners_ = [];
    this.activeMap_ = null;
    this.ready_ = false;

    this.gridSize_ = opt_options.gridSize || 60;
    this.minClusterSize_ = opt_options.minimumClusterSize || 2;
    this.maxZoom_ = opt_options.maxZoom || null;
    this.styles_ = opt_options.styles || [];
    this.title_ = opt_options.title || "";
    this.zoomOnClick_ = true;
    if (opt_options.zoomOnClick !== undefined) {
        this.zoomOnClick_ = opt_options.zoomOnClick;
    }
    this.averageCenter_ = false;
    if (opt_options.averageCenter !== undefined) {
        this.averageCenter_ = opt_options.averageCenter;
    }
    this.ignoreHidden_ = false;
    if (opt_options.ignoreHidden !== undefined) {
        this.ignoreHidden_ = opt_options.ignoreHidden;
    }
    this.printable_ = false;
    if (opt_options.printable !== undefined) {
        this.printable_ = opt_options.printable;
    }
    this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH;
    this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION;
    this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES;
    this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR;
    this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE;
    this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE;

    if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) {
        // Try to avoid IE timeout when processing a huge number of markers:
        this.batchSize_ = this.batchSizeIE_;
    }

    this.setupStyles_();

    this.addMarkers(opt_markers, true);
    this.setMap(map); // Note: this causes onAdd to be called
}


/**
 * Implementation of the onAdd interface method.
 * @ignore
 */
MarkerClusterer.prototype.onAdd = function () {
    var cMarkerClusterer = this;

    this.activeMap_ = this.getMap();
    this.ready_ = true;

    this.repaint();

    // Add the map event listeners
    this.listeners_ = [
      google.maps.event.addListener(this.getMap(), "zoom_changed", function () {
          cMarkerClusterer.resetViewport_(false);
          // Workaround for this Google bug: when map is at level 0 and "-" of
          // zoom slider is clicked, a "zoom_changed" event is fired even though
          // the map doesn't zoom out any further. In this situation, no "idle"
          // event is triggered so the cluster markers that have been removed
          // do not get redrawn.
          if (this.getZoom() === 0) {
              google.maps.event.trigger(this, "idle");
          }
      }),
      google.maps.event.addListener(this.getMap(), "idle", function () {
          cMarkerClusterer.redraw_();
      })
    ];
};


/**
 * Implementation of the onRemove interface method.
 * Removes map event listeners and all cluster icons from the DOM.
 * All managed markers are also put back on the map.
 * @ignore
 */
MarkerClusterer.prototype.onRemove = function () {
    var i;

    // Put all the managed markers back on the map:
    for (i = 0; i < this.markers_.length; i++) {
        this.markers_[i].setMap(this.activeMap_);
    }

    // Remove all clusters:
    for (i = 0; i < this.clusters_.length; i++) {
        this.clusters_[i].remove();
    }
    this.clusters_ = [];

    // Remove map event listeners:
    for (i = 0; i < this.listeners_.length; i++) {
        google.maps.event.removeListener(this.listeners_[i]);
    }
    this.listeners_ = [];

    this.activeMap_ = null;
    this.ready_ = false;
};


/**
 * Implementation of the draw interface method.
 * @ignore
 */
MarkerClusterer.prototype.draw = function () { };


/**
 * Sets up the styles object.
 */
MarkerClusterer.prototype.setupStyles_ = function () {
    var i, size;
    if (this.styles_.length > 0) {
        return;
    }

    for (i = 0; i < this.imageSizes_.length; i++) {
        size = this.imageSizes_[i];
        this.styles_.push({
            url: this.imagePath_ + (i + 1) + "." + this.imageExtension_,
            height: size,
            width: size
        });
    }
};


/**
 *  Fits the map to the bounds of the markers managed by the clusterer.
 */
MarkerClusterer.prototype.fitMapToMarkers = function () {
    var i;
    var markers = this.getMarkers();
    var bounds = new google.maps.LatLngBounds();
    for (i = 0; i < markers.length; i++) {
        bounds.extend(markers[i].getPosition());
    }

    this.getMap().fitBounds(bounds);
};


/**
 * Returns the value of the <code>gridSize</code> property.
 *
 * @return {number} The grid size.
 */
MarkerClusterer.prototype.getGridSize = function () {
    return this.gridSize_;
};


/**
 * Sets the value of the <code>gridSize</code> property.
 *
 * @param {number} gridSize The grid size.
 */
MarkerClusterer.prototype.setGridSize = function (gridSize) {
    this.gridSize_ = gridSize;
};


/**
 * Returns the value of the <code>minimumClusterSize</code> property.
 *
 * @return {number} The minimum cluster size.
 */
MarkerClusterer.prototype.getMinimumClusterSize = function () {
    return this.minClusterSize_;
};

/**
 * Sets the value of the <code>minimumClusterSize</code> property.
 *
 * @param {number} minimumClusterSize The minimum cluster size.
 */
MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) {
    this.minClusterSize_ = minimumClusterSize;
};


/**
 *  Returns the value of the <code>maxZoom</code> property.
 *
 *  @return {number} The maximum zoom level.
 */
MarkerClusterer.prototype.getMaxZoom = function () {
    return this.maxZoom_;
};


/**
 *  Sets the value of the <code>maxZoom</code> property.
 *
 *  @param {number} maxZoom The maximum zoom level.
 */
MarkerClusterer.prototype.setMaxZoom = function (maxZoom) {
    this.maxZoom_ = maxZoom;
};


/**
 *  Returns the value of the <code>styles</code> property.
 *
 *  @return {Array} The array of styles defining the cluster markers to be used.
 */
MarkerClusterer.prototype.getStyles = function () {
    return this.styles_;
};


/**
 *  Sets the value of the <code>styles</code> property.
 *
 *  @param {Array.<ClusterIconStyle>} styles The array of styles to use.
 */
MarkerClusterer.prototype.setStyles = function (styles) {
    this.styles_ = styles;
};


/**
 * Returns the value of the <code>title</code> property.
 *
 * @return {string} The content of the title text.
 */
MarkerClusterer.prototype.getTitle = function () {
    return this.title_;
};


/**
 *  Sets the value of the <code>title</code> property.
 *
 *  @param {string} title The value of the title property.
 */
MarkerClusterer.prototype.setTitle = function (title) {
    this.title_ = title;
};


/**
 * Returns the value of the <code>zoomOnClick</code> property.
 *
 * @return {boolean} True if zoomOnClick property is set.
 */
MarkerClusterer.prototype.getZoomOnClick = function () {
    return this.zoomOnClick_;
};


/**
 *  Sets the value of the <code>zoomOnClick</code> property.
 *
 *  @param {boolean} zoomOnClick The value of the zoomOnClick property.
 */
MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) {
    this.zoomOnClick_ = zoomOnClick;
};


/**
 * Returns the value of the <code>averageCenter</code> property.
 *
 * @return {boolean} True if averageCenter property is set.
 */
MarkerClusterer.prototype.getAverageCenter = function () {
    return this.averageCenter_;
};


/**
 *  Sets the value of the <code>averageCenter</code> property.
 *
 *  @param {boolean} averageCenter The value of the averageCenter property.
 */
MarkerClusterer.prototype.setAverageCenter = function (averageCenter) {
    this.averageCenter_ = averageCenter;
};


/**
 * Returns the value of the <code>ignoreHidden</code> property.
 *
 * @return {boolean} True if ignoreHidden property is set.
 */
MarkerClusterer.prototype.getIgnoreHidden = function () {
    return this.ignoreHidden_;
};


/**
 *  Sets the value of the <code>ignoreHidden</code> property.
 *
 *  @param {boolean} ignoreHidden The value of the ignoreHidden property.
 */
MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) {
    this.ignoreHidden_ = ignoreHidden;
};


/**
 * Returns the value of the <code>imageExtension</code> property.
 *
 * @return {string} The value of the imageExtension property.
 */
MarkerClusterer.prototype.getImageExtension = function () {
    return this.imageExtension_;
};


/**
 *  Sets the value of the <code>imageExtension</code> property.
 *
 *  @param {string} imageExtension The value of the imageExtension property.
 */
MarkerClusterer.prototype.setImageExtension = function (imageExtension) {
    this.imageExtension_ = imageExtension;
};


/**
 * Returns the value of the <code>imagePath</code> property.
 *
 * @return {string} The value of the imagePath property.
 */
MarkerClusterer.prototype.getImagePath = function () {
    return this.imagePath_;
};


/**
 *  Sets the value of the <code>imagePath</code> property.
 *
 *  @param {string} imagePath The value of the imagePath property.
 */
MarkerClusterer.prototype.setImagePath = function (imagePath) {
    this.imagePath_ = imagePath;
};


/**
 * Returns the value of the <code>imageSizes</code> property.
 *
 * @return {Array} The value of the imageSizes property.
 */
MarkerClusterer.prototype.getImageSizes = function () {
    return this.imageSizes_;
};


/**
 *  Sets the value of the <code>imageSizes</code> property.
 *
 *  @param {Array} imageSizes The value of the imageSizes property.
 */
MarkerClusterer.prototype.setImageSizes = function (imageSizes) {
    this.imageSizes_ = imageSizes;
};


/**
 * Returns the value of the <code>calculator</code> property.
 *
 * @return {function} the value of the calculator property.
 */
MarkerClusterer.prototype.getCalculator = function () {
    return this.calculator_;
};


/**
 * Sets the value of the <code>calculator</code> property.
 *
 * @param {function(Array.<google.maps.Marker>, number)} calculator The value
 *  of the calculator property.
 */
MarkerClusterer.prototype.setCalculator = function (calculator) {
    this.calculator_ = calculator;
};


/**
 * Returns the value of the <code>printable</code> property.
 *
 * @return {boolean} the value of the printable property.
 */
MarkerClusterer.prototype.getPrintable = function () {
    return this.printable_;
};


/**
 * Sets the value of the <code>printable</code> property.
 *
 *  @param {boolean} printable The value of the printable property.
 */
MarkerClusterer.prototype.setPrintable = function (printable) {
    this.printable_ = printable;
};


/**
 * Returns the value of the <code>batchSizeIE</code> property.
 *
 * @return {number} the value of the batchSizeIE property.
 */
MarkerClusterer.prototype.getBatchSizeIE = function () {
    return this.batchSizeIE_;
};


/**
 * Sets the value of the <code>batchSizeIE</code> property.
 *
 *  @param {number} batchSizeIE The value of the batchSizeIE property.
 */
MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) {
    this.batchSizeIE_ = batchSizeIE;
};


/**
 *  Returns the array of markers managed by the clusterer.
 *
 *  @return {Array} The array of markers managed by the clusterer.
 */
MarkerClusterer.prototype.getMarkers = function () {
    return this.markers_;
};


/**
 *  Returns the number of markers managed by the clusterer.
 *
 *  @return {number} The number of markers.
 */
MarkerClusterer.prototype.getTotalMarkers = function () {
    return this.markers_.length;
};


/**
 * Returns the current array of clusters formed by the clusterer.
 *
 * @return {Array} The array of clusters formed by the clusterer.
 */
MarkerClusterer.prototype.getClusters = function () {
    return this.clusters_;
};


/**
 * Returns the number of clusters formed by the clusterer.
 *
 * @return {number} The number of clusters formed by the clusterer.
 */
MarkerClusterer.prototype.getTotalClusters = function () {
    return this.clusters_.length;
};


/**
 * Adds a marker to the clusterer. The clusters are redrawn unless
 *  <code>opt_nodraw</code> is set to <code>true</code>.
 *
 * @param {google.maps.Marker} marker The marker to add.
 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
 */
MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) {
    this.pushMarkerTo_(marker);
    if (!opt_nodraw) {
        this.redraw_();
    }
};


/**
 * Adds an array of markers to the clusterer. The clusters are redrawn unless
 *  <code>opt_nodraw</code> is set to <code>true</code>.
 *
 * @param {Array.<google.maps.Marker>} markers The markers to add.
 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
 */
MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) {
    var i;
    for (i = 0; i < markers.length; i++) {
        this.pushMarkerTo_(markers[i]);
    }
    if (!opt_nodraw) {
        this.redraw_();
    }
};


/**
 * Pushes a marker to the clusterer.
 *
 * @param {google.maps.Marker} marker The marker to add.
 */
MarkerClusterer.prototype.pushMarkerTo_ = function (marker) {
    // If the marker is draggable add a listener so we can update the clusters on the dragend:
    if (marker.getDraggable()) {
        var cMarkerClusterer = this;
        google.maps.event.addListener(marker, "dragend", function () {
            if (cMarkerClusterer.ready_) {
                this.isAdded = false;
                cMarkerClusterer.repaint();
            }
        });
    }
    marker.isAdded = false;
    this.markers_.push(marker);
};


/**
 * Removes a marker from the cluster.  The clusters are redrawn unless
 *  <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the
 *  marker was removed from the clusterer.
 *
 * @param {google.maps.Marker} marker The marker to remove.
 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
 * @return {boolean} True if the marker was removed from the clusterer.
 */
MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) {
    var removed = this.removeMarker_(marker);

    if (!opt_nodraw && removed) {
        this.repaint();
    }

    return removed;
};


/**
 * Removes an array of markers from the cluster. The clusters are redrawn unless
 *  <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers
 *  were removed from the clusterer.
 *
 * @param {Array.<google.maps.Marker>} markers The markers to remove.
 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
 * @return {boolean} True if markers were removed from the clusterer.
 */
MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) {
    var i, r;
    var removed = false;

    for (i = 0; i < markers.length; i++) {
        r = this.removeMarker_(markers[i]);
        removed = removed || r;
    }

    if (!opt_nodraw && removed) {
        this.repaint();
    }

    return removed;
};


/**
 * Removes a marker and returns true if removed, false if not.
 *
 * @param {google.maps.Marker} marker The marker to remove
 * @return {boolean} Whether the marker was removed or not
 */
MarkerClusterer.prototype.removeMarker_ = function (marker) {
    var i;
    var index = -1;
    if (this.markers_.indexOf) {
        index = this.markers_.indexOf(marker);
    } else {
        for (i = 0; i < this.markers_.length; i++) {
            if (marker === this.markers_[i]) {
                index = i;
                break;
            }
        }
    }

    if (index === -1) {
        // Marker is not in our list of markers, so do nothing:
        return false;
    }

    marker.setMap(null);
    this.markers_.splice(index, 1); // Remove the marker from the list of managed markers
    return true;
};


/**
 * Removes all clusters and markers from the map and also removes all markers
 *  managed by the clusterer.
 */
MarkerClusterer.prototype.clearMarkers = function () {
    this.resetViewport_(true);
    this.markers_ = [];
};


/**
 * Recalculates and redraws all the marker clusters from scratch.
 *  Call this after changing any properties.
 */
MarkerClusterer.prototype.repaint = function () {
    var oldClusters = this.clusters_.slice();
    this.clusters_ = [];
    this.resetViewport_(false);
    this.redraw_();

    // Remove the old clusters.
    // Do it in a timeout to prevent blinking effect.
    setTimeout(function () {
        var i;
        for (i = 0; i < oldClusters.length; i++) {
            oldClusters[i].remove();
        }
    }, 0);
};


/**
 * Returns the current bounds extended by the grid size.
 *
 * @param {google.maps.LatLngBounds} bounds The bounds to extend.
 * @return {google.maps.LatLngBounds} The extended bounds.
 * @ignore
 */
MarkerClusterer.prototype.getExtendedBounds = function (bounds) {
    var projection = this.getProjection();

    // Turn the bounds into latlng.
    var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
        bounds.getNorthEast().lng());
    var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
        bounds.getSouthWest().lng());

    // Convert the points to pixels and the extend out by the grid size.
    var trPix = projection.fromLatLngToDivPixel(tr);
    trPix.x += this.gridSize_;
    trPix.y -= this.gridSize_;

    var blPix = projection.fromLatLngToDivPixel(bl);
    blPix.x -= this.gridSize_;
    blPix.y += this.gridSize_;

    // Convert the pixel points back to LatLng
    var ne = projection.fromDivPixelToLatLng(trPix);
    var sw = projection.fromDivPixelToLatLng(blPix);

    // Extend the bounds to contain the new bounds.
    bounds.extend(ne);
    bounds.extend(sw);

    return bounds;
};


/**
 * Redraws all the clusters.
 */
MarkerClusterer.prototype.redraw_ = function () {
    this.createClusters_(0);
};


/**
 * Removes all clusters from the map. The markers are also removed from the map
 *  if <code>opt_hide</code> is set to <code>true</code>.
 *
 * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers
 *  from the map.
 */
MarkerClusterer.prototype.resetViewport_ = function (opt_hide) {
    var i, marker;
    // Remove all the clusters
    for (i = 0; i < this.clusters_.length; i++) {
        this.clusters_[i].remove();
    }
    this.clusters_ = [];

    // Reset the markers to not be added and to be removed from the map.
    for (i = 0; i < this.markers_.length; i++) {
        marker = this.markers_[i];
        marker.isAdded = false;
        if (opt_hide) {
            marker.setMap(null);
        }
    }
};


/**
 * Calculates the distance between two latlng locations in km.
 *
 * @param {google.maps.LatLng} p1 The first lat lng point.
 * @param {google.maps.LatLng} p2 The second lat lng point.
 * @return {number} The distance between the two points in km.
 * @see http://www.movable-type.co.uk/scripts/latlong.html
*/
MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) {
    var R = 6371; // Radius of the Earth in km
    var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
    var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
      Math.sin(dLon / 2) * Math.sin(dLon / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = R * c;
    return d;
};


/**
 * Determines if a marker is contained in a bounds.
 *
 * @param {google.maps.Marker} marker The marker to check.
 * @param {google.maps.LatLngBounds} bounds The bounds to check against.
 * @return {boolean} True if the marker is in the bounds.
 */
MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) {
    return bounds.contains(marker.getPosition());
};


/**
 * Adds a marker to a cluster, or creates a new cluster.
 *
 * @param {google.maps.Marker} marker The marker to add.
 */
MarkerClusterer.prototype.addToClosestCluster_ = function (marker) {
    var i, d, cluster, center;
    var distance = 40000; // Some large number
    var clusterToAddTo = null;
    for (i = 0; i < this.clusters_.length; i++) {
        cluster = this.clusters_[i];
        center = cluster.getCenter();
        if (center) {
            d = this.distanceBetweenPoints_(center, marker.getPosition());
            if (d < distance) {
                distance = d;
                clusterToAddTo = cluster;
            }
        }
    }

    if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
        clusterToAddTo.addMarker(marker);
    } else {
        cluster = new Cluster(this);
        cluster.addMarker(marker);
        this.clusters_.push(cluster);
    }
};


/**
 * Creates the clusters. This is done in batches to avoid timeout errors
 *  in some browsers when there is a huge number of markers.
 *
 * @param {number} iFirst The index of the first marker in the batch of
 *  markers to be added to clusters.
 */
MarkerClusterer.prototype.createClusters_ = function (iFirst) {
    var i, marker;
    var mapBounds;
    var cMarkerClusterer = this;
    if (!this.ready_) {
        return;
    }

    // Cancel previous batch processing if we're working on the first batch:
    if (iFirst === 0) {
        /**
         * This event is fired when the <code>MarkerClusterer</code> begins
         *  clustering markers.
         * @name MarkerClusterer#clusteringbegin
         * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
         * @event
         */
        google.maps.event.trigger(this, "clusteringbegin", this);

        if (typeof this.timerRefStatic !== "undefined") {
            clearTimeout(this.timerRefStatic);
            delete this.timerRefStatic;
        }
    }

    // Get our current map view bounds.
    // Create a new bounds object so we don't affect the map.
    //
    // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug:
    if (this.getMap().getZoom() > 3) {
        mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(),
          this.getMap().getBounds().getNorthEast());
    } else {
        mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625));
    }
    var bounds = this.getExtendedBounds(mapBounds);

    var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length);

    for (i = iFirst; i < iLast; i++) {
        marker = this.markers_[i];
        if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
            if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) {
                this.addToClosestCluster_(marker);
            }
        }
    }

    if (iLast < this.markers_.length) {
        this.timerRefStatic = setTimeout(function () {
            cMarkerClusterer.createClusters_(iLast);
        }, 0);
    } else {
        delete this.timerRefStatic;

        /**
         * This event is fired when the <code>MarkerClusterer</code> stops
         *  clustering markers.
         * @name MarkerClusterer#clusteringend
         * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
         * @event
         */
        google.maps.event.trigger(this, "clusteringend", this);
    }
};


/**
 * Extends an object's prototype by another's.
 *
 * @param {Object} obj1 The object to be extended.
 * @param {Object} obj2 The object to extend with.
 * @return {Object} The new extended object.
 * @ignore
 */
MarkerClusterer.prototype.extend = function (obj1, obj2) {
    return (function (object) {
        var property;
        for (property in object.prototype) {
            this.prototype[property] = object.prototype[property];
        }
        return this;
    }).apply(obj1, [obj2]);
};


/**
 * The default function for determining the label text and style
 * for a cluster icon.
 *
 * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster.
 * @param {number} numStyles The number of marker styles available.
 * @return {ClusterIconInfo} The information resource for the cluster.
 * @constant
 * @ignore
 */
MarkerClusterer.CALCULATOR = function (markers, numStyles) {
    var index = 0;
    var count = markers.length.toString();

    var dv = count;
    while (dv !== 0) {
        dv = parseInt(dv / 10, 10);
        index++;
    }

    index = Math.min(index, numStyles);
    return {
        text: count,
        index: index
    };
};


/**
 * The number of markers to process in one batch.
 *
 * @type {number}
 * @constant
 */
MarkerClusterer.BATCH_SIZE = 2000;


/**
 * The number of markers to process in one batch (IE only).
 *
 * @type {number}
 * @constant
 */
MarkerClusterer.BATCH_SIZE_IE = 500;


/**
 * The default root name for the marker cluster images.
 *
 * @type {string}
 * @constant
 */
MarkerClusterer.IMAGE_PATH = "/images/Google/m";


/**
 * The default extension name for the marker cluster images.
 *
 * @type {string}
 * @constant
 */
MarkerClusterer.IMAGE_EXTENSION = "png";


/**
 * The default array of sizes for the marker cluster images.
 *
 * @type {Array.<number>}
 * @constant
 */
MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90];;
var OfficeLocator = (function () {
    function OfficeLocator(officeList, mapElement, searchBoxElement, currentLocationButtonElement, templateDelegate) {

        var _this = this;
        this.mapState = {};
        this.markersArray = [];

        this.initMap(mapElement);

        //this.initSearchBox(searchBoxElement);
        //this.initCurrentLocationButton(currentLocationButtonElement);
        var oms = new OverlappingMarkerSpiderfier(this.map, { keepSpiderfied: true, markersWontMove: true, markersWontHide: true });

        _.each(officeList, function (item) {
            var marker = _this.addMarker(item);
            _this.markersArray.push(marker);
            oms.addMarker(marker);
        });

        this.zoom(officeList);

        this.initInfoBubble(templateDelegate, oms);
    }
    OfficeLocator.prototype.initMap = function (mapElement) {
        this.map = new google.maps.Map(mapElement, {
            center: new google.maps.LatLng(54.44, -4.04),
            zoom: 6,
            minZoom: 4,
            maxZoom: 17,
            disableDefaultUI: true,
            panControl: true,
            zoomControl: true,
            scaleControl: true,
            streetViewControl: true,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        });
    };

    OfficeLocator.prototype.initSearchBox = function (searchBoxElement) {
        var _this = this;
        this.searchBox = new google.maps.places.Autocomplete(searchBoxElement, { types: ['(regions)'], componentRestrictions: { country: 'us' }, bounds: undefined });

        google.maps.event.addListener(this.searchBox, 'place_changed', function () {
            var place = _this.searchBox.getPlace();
            if (place.geometry && place.geometry.location) {
                var closestMarker = _this.findClosestMarker(place.geometry.location.lat(), place.geometry.location.lng());
                _this.map.setCenter(closestMarker.getPosition());
                _this.map.setZoom(12);
            }
        });
    };

    OfficeLocator.prototype.initCurrentLocationButton = function (currentLocationButtonElement) {
        var _this = this;
        currentLocationButtonElement.onclick = function () {
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(function (position) {
                    var closestMarker = _this.findClosestMarker(position.coords.latitude, position.coords.longitude);
                    _this.map.setCenter(closestMarker.getPosition());
                    _this.map.setZoom(12);
                });
            }
        };
    };

    OfficeLocator.prototype.initInfoBubble = function (templateDelegate, oms) {
        var _this = this;
        this.infoBubbleManager = new InfoBubbleManager(this.map, templateDelegate);

        oms.addListener('click', function (marker, event) {
            return _this.infoBubbleManager.showBubble(marker, event);
        });

        google.maps.event.addListener(this.map, 'click', function () {
            return _this.infoBubbleManager.closeBubble();
        });
    };

    OfficeLocator.prototype.addMarker = function (item) {
        var icon = new google.maps.MarkerImage("/images/google/marker.png");
        icon.size = new google.maps.Size(60, 40);
        icon.anchor = new google.maps.Point(11, 40);

        item.url = item.ViewLink;
        var marker = new google.maps.Marker({
            position: new google.maps.LatLng(item.Latitude, item.Longitude),
            title: item.Name + ', ' + item.State,
            icon: icon,
            map: this.map,
            item: item,
        });

        return marker;
    };

    OfficeLocator.prototype.zoom = function (selectedOffices) {
        if (this.mapState.center && this.mapState.zoom) {
            this.map.setCenter(this.mapState.center);
            this.map.setZoom(this.mapState.zoom);
        }
        var latlng = _.map(selectedOffices, function (item) {
            return new google.maps.LatLng(item.Latitude, item.Longitude)
        });

        var latlngbounds = new google.maps.LatLngBounds();
        _.each(latlng, function (n) {
            latlngbounds.extend(n);
        });

        this.map.setCenter(latlngbounds.getCenter());
        this.map.fitBounds(latlngbounds);
    };

    OfficeLocator.prototype.zoomOrCenterChanged = function () {
        this.mapState = {
            center: this.map.getCenter(),
            zoom: this.map.getZoom()
        };
    };

    OfficeLocator.prototype.findClosestMarker = function (lat, lng) {
        var R = 6371;
        var distances = [];
        var closest = -1;
        for (var i = 0; i < this.markersArray.length; i++) {
            var mlat = this.markersArray[i].position.lat();
            var mlng = this.markersArray[i].position.lng();
            var dLat = this.rad(mlat - lat);
            var dLong = this.rad(mlng - lng);
            var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.rad(lat)) * Math.cos(this.rad(lat)) * Math.sin(dLong / 2) * Math.sin(dLong / 2);
            var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
            var d = R * c;
            distances[i] = d;
            if (closest == -1 || d < distances[closest]) {
                closest = i;
            }
        }
        return this.markersArray[closest];
    };

    OfficeLocator.prototype.rad = function (x) {
        return x * Math.PI / 180;
    };

    OfficeLocator.prototype.calcRoute = function (lat, lng) {
        var _this = this;
        this.infoBubbleManager.closeBubble();
        navigator.geolocation.getCurrentPosition(function (position) {
            var dest = new google.maps.LatLng(lat, lng);
            var currentLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);

            var directionsService = new google.maps.DirectionsService();
            var directionsDisplay = new google.maps.DirectionsRenderer();

            directionsDisplay.setMap(_this.map);

            var request = {
                origin: currentLocation,
                destination: dest,
                travelMode: google.maps.TravelMode.DRIVING
            };

            directionsService.route(request, function (response, status) {
                if (status == google.maps.DirectionsStatus.OK) {
                    directionsDisplay.setDirections(response);
                }
            });
        });
    };
    return OfficeLocator;
})();
;
(function(){/*
 OverlappingMarkerSpiderfier
https://github.com/jawj/OverlappingMarkerSpiderfier
Copyright (c) 2011 - 2012 George MacKerron
Released under the MIT licence: http://opensource.org/licenses/mit-license
Note: The Google Maps API v3 must be included *before* this code
*/
var h=!0,u=null,v=!1;
(function(){var A,B={}.hasOwnProperty,C=[].slice;if(((A=this.google)!=u?A.maps:void 0)!=u)this.OverlappingMarkerSpiderfier=function(){function w(b,d){var a,g,f,e,c=this;this.map=b;d==u&&(d={});for(a in d)B.call(d,a)&&(g=d[a],this[a]=g);this.e=new this.constructor.g(this.map);this.n();this.b={};e=["click","zoom_changed","maptypeid_changed"];g=0;for(f=e.length;g<f;g++)a=e[g],p.addListener(this.map,a,function(){return c.unspiderfy()})}var p,s,t,q,k,c,y,z;c=w.prototype;z=[w,c];q=0;for(k=z.length;q<k;q++)t=
z[q],t.VERSION="0.3.3";s=google.maps;p=s.event;k=s.MapTypeId;y=2*Math.PI;c.keepSpiderfied=v;c.markersWontHide=v;c.markersWontMove=v;c.nearbyDistance=20;c.circleSpiralSwitchover=9;c.circleFootSeparation=23;c.circleStartAngle=y/12;c.spiralFootSeparation=26;c.spiralLengthStart=11;c.spiralLengthFactor=4;c.spiderfiedZIndex=1E3;c.usualLegZIndex=10;c.highlightedLegZIndex=20;c.legWeight=1.5;c.legColors={usual:{},highlighted:{}};q=c.legColors.usual;t=c.legColors.highlighted;q[k.HYBRID]=q[k.SATELLITE]="#fff";
t[k.HYBRID]=t[k.SATELLITE]="#f00";q[k.TERRAIN]=q[k.ROADMAP]="#444";t[k.TERRAIN]=t[k.ROADMAP]="#f00";c.n=function(){this.a=[];this.j=[]};c.addMarker=function(b){var d,a=this;if(b._oms!=u)return this;b._oms=h;d=[p.addListener(b,"click",function(d){return a.F(b,d)})];this.markersWontHide||d.push(p.addListener(b,"visible_changed",function(){return a.o(b,v)}));this.markersWontMove||d.push(p.addListener(b,"position_changed",function(){return a.o(b,h)}));this.j.push(d);this.a.push(b);return this};c.o=function(b,
d){if(b._omsData!=u&&(d||!b.getVisible())&&!(this.s!=u||this.t!=u))return this.unspiderfy(d?b:u)};c.getMarkers=function(){return this.a.slice(0)};c.removeMarker=function(b){var d,a,g,f,e;b._omsData!=u&&this.unspiderfy();d=this.m(this.a,b);if(0>d)return this;g=this.j.splice(d,1)[0];f=0;for(e=g.length;f<e;f++)a=g[f],p.removeListener(a);delete b._oms;this.a.splice(d,1);return this};c.clearMarkers=function(){var b,d,a,g,f,e,c,m;this.unspiderfy();m=this.a;b=g=0;for(e=m.length;g<e;b=++g){a=m[b];d=this.j[b];
f=0;for(c=d.length;f<c;f++)b=d[f],p.removeListener(b);delete a._oms}this.n();return this};c.addListener=function(b,d){var a,g;((g=(a=this.b)[b])!=u?g:a[b]=[]).push(d);return this};c.removeListener=function(b,d){var a;a=this.m(this.b[b],d);0>a||this.b[b].splice(a,1);return this};c.clearListeners=function(b){this.b[b]=[];return this};c.trigger=function(){var b,d,a,g,f,e;d=arguments[0];b=2<=arguments.length?C.call(arguments,1):[];d=(a=this.b[d])!=u?a:[];e=[];g=0;for(f=d.length;g<f;g++)a=d[g],e.push(a.apply(u,
b));return e};c.u=function(b,d){var a,g,f,e,c;f=this.circleFootSeparation*(2+b)/y;g=y/b;c=[];for(a=e=0;0<=b?e<b:e>b;a=0<=b?++e:--e)a=this.circleStartAngle+a*g,c.push(new s.Point(d.x+f*Math.cos(a),d.y+f*Math.sin(a)));return c};c.v=function(b,d){var a,g,f,e,c;f=this.spiralLengthStart;a=0;c=[];for(g=e=0;0<=b?e<b:e>b;g=0<=b?++e:--e)a+=this.spiralFootSeparation/f+5E-4*g,g=new s.Point(d.x+f*Math.cos(a),d.y+f*Math.sin(a)),f+=y*this.spiralLengthFactor/a,c.push(g);return c};c.F=function(b,d){var a,g,f,e,c,
m,l,x,n;e=b._omsData!=u;(!e||!this.keepSpiderfied)&&this.unspiderfy();if(e||this.map.getStreetView().getVisible()||"GoogleEarthAPI"===this.map.getMapTypeId())return this.trigger("click",b,d);e=[];c=[];a=this.nearbyDistance;m=a*a;f=this.c(b.position);n=this.a;l=0;for(x=n.length;l<x;l++)a=n[l],a.map!=u&&a.getVisible()&&(g=this.c(a.position),this.f(g,f)<m?e.push({A:a,p:g}):c.push(a));return 1===e.length?this.trigger("click",b,d):this.G(e,c)};c.markersNearMarker=function(b,d){var a,g,f,e,c,m,l,x,n,p;
d==u&&(d=v);if(this.e.getProjection()==u)throw"Must wait for 'idle' event on map before calling markersNearMarker";a=this.nearbyDistance;c=a*a;f=this.c(b.position);e=[];x=this.a;m=0;for(l=x.length;m<l;m++)if(a=x[m],!(a===b||a.map==u||!a.getVisible()))if(g=this.c((n=(p=a._omsData)!=u?p.l:void 0)!=u?n:a.position),this.f(g,f)<c&&(e.push(a),d))break;return e};c.markersNearAnyOtherMarker=function(){var b,d,a,g,c,e,r,m,l,p,n,k;if(this.e.getProjection()==u)throw"Must wait for 'idle' event on map before calling markersNearAnyOtherMarker";
e=this.nearbyDistance;b=e*e;g=this.a;e=[];n=0;for(a=g.length;n<a;n++)d=g[n],e.push({q:this.c((r=(l=d._omsData)!=u?l.l:void 0)!=u?r:d.position),d:v});n=this.a;d=r=0;for(l=n.length;r<l;d=++r)if(a=n[d],a.map!=u&&a.getVisible()&&(g=e[d],!g.d)){k=this.a;a=m=0;for(p=k.length;m<p;a=++m)if(c=k[a],a!==d&&(c.map!=u&&c.getVisible())&&(c=e[a],(!(a<d)||c.d)&&this.f(g.q,c.q)<b)){g.d=c.d=h;break}}n=this.a;a=[];b=r=0;for(l=n.length;r<l;b=++r)d=n[b],e[b].d&&a.push(d);return a};c.z=function(b){var d=this;return{h:function(){return b._omsData.i.setOptions({strokeColor:d.legColors.highlighted[d.map.mapTypeId],
zIndex:d.highlightedLegZIndex})},k:function(){return b._omsData.i.setOptions({strokeColor:d.legColors.usual[d.map.mapTypeId],zIndex:d.usualLegZIndex})}}};c.G=function(b,d){var a,c,f,e,r,m,l,k,n,q;this.s=h;q=b.length;a=this.C(function(){var a,d,c;c=[];a=0;for(d=b.length;a<d;a++)k=b[a],c.push(k.p);return c}());e=q>=this.circleSpiralSwitchover?this.v(q,a).reverse():this.u(q,a);a=function(){var a,d,k,q=this;k=[];a=0;for(d=e.length;a<d;a++)f=e[a],c=this.D(f),n=this.B(b,function(a){return q.f(a.p,f)}),
l=n.A,m=new s.Polyline({map:this.map,path:[l.position,c],strokeColor:this.legColors.usual[this.map.mapTypeId],strokeWeight:this.legWeight,zIndex:this.usualLegZIndex}),l._omsData={l:l.position,i:m},this.legColors.highlighted[this.map.mapTypeId]!==this.legColors.usual[this.map.mapTypeId]&&(r=this.z(l),l._omsData.w={h:p.addListener(l,"mouseover",r.h),k:p.addListener(l,"mouseout",r.k)}),l.setPosition(c),l.setZIndex(Math.round(this.spiderfiedZIndex+f.y)),k.push(l);return k}.call(this);delete this.s;this.r=
h;return this.trigger("spiderfy",a,d)};c.unspiderfy=function(b){var d,a,c,f,e,k,m;b==u&&(b=u);if(this.r==u)return this;this.t=h;f=[];c=[];m=this.a;e=0;for(k=m.length;e<k;e++)a=m[e],a._omsData!=u?(a._omsData.i.setMap(u),a!==b&&a.setPosition(a._omsData.l),a.setZIndex(u),d=a._omsData.w,d!=u&&(p.removeListener(d.h),p.removeListener(d.k)),delete a._omsData,f.push(a)):c.push(a);delete this.t;delete this.r;this.trigger("unspiderfy",f,c);return this};c.f=function(b,d){var a,c;a=b.x-d.x;c=b.y-d.y;return a*
a+c*c};c.C=function(b){var d,a,c,f,e;f=a=c=0;for(e=b.length;f<e;f++)d=b[f],a+=d.x,c+=d.y;b=b.length;return new s.Point(a/b,c/b)};c.c=function(b){return this.e.getProjection().fromLatLngToDivPixel(b)};c.D=function(b){return this.e.getProjection().fromDivPixelToLatLng(b)};c.B=function(b,c){var a,g,f,e,k,m;f=k=0;for(m=b.length;k<m;f=++k)if(e=b[f],e=c(e),"undefined"===typeof a||a===u||e<g)g=e,a=f;return b.splice(a,1)[0]};c.m=function(b,c){var a,g,f,e;if(b.indexOf!=u)return b.indexOf(c);a=f=0;for(e=b.length;f<
e;a=++f)if(g=b[a],g===c)return a;return-1};w.g=function(b){return this.setMap(b)};w.g.prototype=new s.OverlayView;w.g.prototype.draw=function(){};return w}()}).call(this);}).call(this);
/* Tue 7 May 2013 14:56:02 BST */;
// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function () {
    var cache = {};

    this.tmpl = function tmpl(str, data) {
        // Figure out if we're getting a template, or if we need to
        // load the template - and be sure to cache the result.
        var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :

        // Generate a reusable function that will serve as a template
        // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +

        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +

        // Convert the template into pure JavaScript
        str
          .replace(/[\r\t\n]/g, " ")
          .split("{%").join("\t")
          .replace(/((^|%})[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%}/g, "',$1,'")
          .split("\t").join("');")
          .split("%}").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');");

        // Provide some basic currying to the user
        return data ? fn(data) : fn;
    };
})();;
(function(){/*
 OverlappingMarkerSpiderfier
https://github.com/jawj/OverlappingMarkerSpiderfier
Copyright (c) 2011 - 2012 George MacKerron
Released under the MIT licence: http://opensource.org/licenses/mit-license
Note: The Google Maps API v3 must be included *before* this code
*/
var h=!0,u=null,v=!1;
(function(){var A,B={}.hasOwnProperty,C=[].slice;if(((A=this.google)!=u?A.maps:void 0)!=u)this.OverlappingMarkerSpiderfier=function(){function w(b,d){var a,g,f,e,c=this;this.map=b;d==u&&(d={});for(a in d)B.call(d,a)&&(g=d[a],this[a]=g);this.e=new this.constructor.g(this.map);this.n();this.b={};e=["click","zoom_changed","maptypeid_changed"];g=0;for(f=e.length;g<f;g++)a=e[g],p.addListener(this.map,a,function(){return c.unspiderfy()})}var p,s,t,q,k,c,y,z;c=w.prototype;z=[w,c];q=0;for(k=z.length;q<k;q++)t=
z[q],t.VERSION="0.3.3";s=google.maps;p=s.event;k=s.MapTypeId;y=2*Math.PI;c.keepSpiderfied=v;c.markersWontHide=v;c.markersWontMove=v;c.nearbyDistance=20;c.circleSpiralSwitchover=9;c.circleFootSeparation=23;c.circleStartAngle=y/12;c.spiralFootSeparation=26;c.spiralLengthStart=11;c.spiralLengthFactor=4;c.spiderfiedZIndex=1E3;c.usualLegZIndex=10;c.highlightedLegZIndex=20;c.legWeight=1.5;c.legColors={usual:{},highlighted:{}};q=c.legColors.usual;t=c.legColors.highlighted;q[k.HYBRID]=q[k.SATELLITE]="#fff";
t[k.HYBRID]=t[k.SATELLITE]="#f00";q[k.TERRAIN]=q[k.ROADMAP]="#444";t[k.TERRAIN]=t[k.ROADMAP]="#f00";c.n=function(){this.a=[];this.j=[]};c.addMarker=function(b){var d,a=this;if(b._oms!=u)return this;b._oms=h;d=[p.addListener(b,"click",function(d){return a.F(b,d)})];this.markersWontHide||d.push(p.addListener(b,"visible_changed",function(){return a.o(b,v)}));this.markersWontMove||d.push(p.addListener(b,"position_changed",function(){return a.o(b,h)}));this.j.push(d);this.a.push(b);return this};c.o=function(b,
d){if(b._omsData!=u&&(d||!b.getVisible())&&!(this.s!=u||this.t!=u))return this.unspiderfy(d?b:u)};c.getMarkers=function(){return this.a.slice(0)};c.removeMarker=function(b){var d,a,g,f,e;b._omsData!=u&&this.unspiderfy();d=this.m(this.a,b);if(0>d)return this;g=this.j.splice(d,1)[0];f=0;for(e=g.length;f<e;f++)a=g[f],p.removeListener(a);delete b._oms;this.a.splice(d,1);return this};c.clearMarkers=function(){var b,d,a,g,f,e,c,m;this.unspiderfy();m=this.a;b=g=0;for(e=m.length;g<e;b=++g){a=m[b];d=this.j[b];
f=0;for(c=d.length;f<c;f++)b=d[f],p.removeListener(b);delete a._oms}this.n();return this};c.addListener=function(b,d){var a,g;((g=(a=this.b)[b])!=u?g:a[b]=[]).push(d);return this};c.removeListener=function(b,d){var a;a=this.m(this.b[b],d);0>a||this.b[b].splice(a,1);return this};c.clearListeners=function(b){this.b[b]=[];return this};c.trigger=function(){var b,d,a,g,f,e;d=arguments[0];b=2<=arguments.length?C.call(arguments,1):[];d=(a=this.b[d])!=u?a:[];e=[];g=0;for(f=d.length;g<f;g++)a=d[g],e.push(a.apply(u,
b));return e};c.u=function(b,d){var a,g,f,e,c;f=this.circleFootSeparation*(2+b)/y;g=y/b;c=[];for(a=e=0;0<=b?e<b:e>b;a=0<=b?++e:--e)a=this.circleStartAngle+a*g,c.push(new s.Point(d.x+f*Math.cos(a),d.y+f*Math.sin(a)));return c};c.v=function(b,d){var a,g,f,e,c;f=this.spiralLengthStart;a=0;c=[];for(g=e=0;0<=b?e<b:e>b;g=0<=b?++e:--e)a+=this.spiralFootSeparation/f+5E-4*g,g=new s.Point(d.x+f*Math.cos(a),d.y+f*Math.sin(a)),f+=y*this.spiralLengthFactor/a,c.push(g);return c};c.F=function(b,d){var a,g,f,e,c,
m,l,x,n;e=b._omsData!=u;(!e||!this.keepSpiderfied)&&this.unspiderfy();if(e||this.map.getStreetView().getVisible()||"GoogleEarthAPI"===this.map.getMapTypeId())return this.trigger("click",b,d);e=[];c=[];a=this.nearbyDistance;m=a*a;f=this.c(b.position);n=this.a;l=0;for(x=n.length;l<x;l++)a=n[l],a.map!=u&&a.getVisible()&&(g=this.c(a.position),this.f(g,f)<m?e.push({A:a,p:g}):c.push(a));return 1===e.length?this.trigger("click",b,d):this.G(e,c)};c.markersNearMarker=function(b,d){var a,g,f,e,c,m,l,x,n,p;
d==u&&(d=v);if(this.e.getProjection()==u)throw"Must wait for 'idle' event on map before calling markersNearMarker";a=this.nearbyDistance;c=a*a;f=this.c(b.position);e=[];x=this.a;m=0;for(l=x.length;m<l;m++)if(a=x[m],!(a===b||a.map==u||!a.getVisible()))if(g=this.c((n=(p=a._omsData)!=u?p.l:void 0)!=u?n:a.position),this.f(g,f)<c&&(e.push(a),d))break;return e};c.markersNearAnyOtherMarker=function(){var b,d,a,g,c,e,r,m,l,p,n,k;if(this.e.getProjection()==u)throw"Must wait for 'idle' event on map before calling markersNearAnyOtherMarker";
e=this.nearbyDistance;b=e*e;g=this.a;e=[];n=0;for(a=g.length;n<a;n++)d=g[n],e.push({q:this.c((r=(l=d._omsData)!=u?l.l:void 0)!=u?r:d.position),d:v});n=this.a;d=r=0;for(l=n.length;r<l;d=++r)if(a=n[d],a.map!=u&&a.getVisible()&&(g=e[d],!g.d)){k=this.a;a=m=0;for(p=k.length;m<p;a=++m)if(c=k[a],a!==d&&(c.map!=u&&c.getVisible())&&(c=e[a],(!(a<d)||c.d)&&this.f(g.q,c.q)<b)){g.d=c.d=h;break}}n=this.a;a=[];b=r=0;for(l=n.length;r<l;b=++r)d=n[b],e[b].d&&a.push(d);return a};c.z=function(b){var d=this;return{h:function(){return b._omsData.i.setOptions({strokeColor:d.legColors.highlighted[d.map.mapTypeId],
zIndex:d.highlightedLegZIndex})},k:function(){return b._omsData.i.setOptions({strokeColor:d.legColors.usual[d.map.mapTypeId],zIndex:d.usualLegZIndex})}}};c.G=function(b,d){var a,c,f,e,r,m,l,k,n,q;this.s=h;q=b.length;a=this.C(function(){var a,d,c;c=[];a=0;for(d=b.length;a<d;a++)k=b[a],c.push(k.p);return c}());e=q>=this.circleSpiralSwitchover?this.v(q,a).reverse():this.u(q,a);a=function(){var a,d,k,q=this;k=[];a=0;for(d=e.length;a<d;a++)f=e[a],c=this.D(f),n=this.B(b,function(a){return q.f(a.p,f)}),
l=n.A,m=new s.Polyline({map:this.map,path:[l.position,c],strokeColor:this.legColors.usual[this.map.mapTypeId],strokeWeight:this.legWeight,zIndex:this.usualLegZIndex}),l._omsData={l:l.position,i:m},this.legColors.highlighted[this.map.mapTypeId]!==this.legColors.usual[this.map.mapTypeId]&&(r=this.z(l),l._omsData.w={h:p.addListener(l,"mouseover",r.h),k:p.addListener(l,"mouseout",r.k)}),l.setPosition(c),l.setZIndex(Math.round(this.spiderfiedZIndex+f.y)),k.push(l);return k}.call(this);delete this.s;this.r=
h;return this.trigger("spiderfy",a,d)};c.unspiderfy=function(b){var d,a,c,f,e,k,m;b==u&&(b=u);if(this.r==u)return this;this.t=h;f=[];c=[];m=this.a;e=0;for(k=m.length;e<k;e++)a=m[e],a._omsData!=u?(a._omsData.i.setMap(u),a!==b&&a.setPosition(a._omsData.l),a.setZIndex(u),d=a._omsData.w,d!=u&&(p.removeListener(d.h),p.removeListener(d.k)),delete a._omsData,f.push(a)):c.push(a);delete this.t;delete this.r;this.trigger("unspiderfy",f,c);return this};c.f=function(b,d){var a,c;a=b.x-d.x;c=b.y-d.y;return a*
a+c*c};c.C=function(b){var d,a,c,f,e;f=a=c=0;for(e=b.length;f<e;f++)d=b[f],a+=d.x,c+=d.y;b=b.length;return new s.Point(a/b,c/b)};c.c=function(b){return this.e.getProjection().fromLatLngToDivPixel(b)};c.D=function(b){return this.e.getProjection().fromDivPixelToLatLng(b)};c.B=function(b,c){var a,g,f,e,k,m;f=k=0;for(m=b.length;k<m;f=++k)if(e=b[f],e=c(e),"undefined"===typeof a||a===u||e<g)g=e,a=f;return b.splice(a,1)[0]};c.m=function(b,c){var a,g,f,e;if(b.indexOf!=u)return b.indexOf(c);a=f=0;for(e=b.length;f<
e;a=++f)if(g=b[a],g===c)return a;return-1};w.g=function(b){return this.setMap(b)};w.g.prototype=new s.OverlayView;w.g.prototype.draw=function(){};return w}()}).call(this);}).call(this);
/* Tue 7 May 2013 14:56:02 BST */;
