Subversion Repositories eFlore/Applications.cel

Compare Revisions

Ignore whitespace Rev 498 → Rev 500

/trunk/widget/modules/carto/squelettes/markerclusterer_compiled.js
1,0 → 0,0
function d(a){return function(b){this[a]=b}}function f(a){return function(){return this[a]}}var g; function i(a,b,c){this.extend(i,google.maps.OverlayView);this.b=a;this.a=[];this.l=[];this.V=[53,56,66,78,90];this.j=[];this.v=false;c=c||{};this.f=c.gridSize||60;this.R=c.maxZoom||null;this.j=c.styles||[];this.Q=c.imagePath||this.J;this.P=c.imageExtension||this.I;this.W=c.zoomOnClick||true;k(this);this.setMap(a);this.D=this.b.getZoom();var e=this;google.maps.event.addListener(this.b,"zoom_changed",function(){if(this.D!=e.b.getZoom()){this.D=e.b.getZoom();e.m()}});google.maps.event.addListener(this.b, "bounds_changed",function(){e.i()});b&&b.length&&this.z(b,false)}g=i.prototype;g.J="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m";g.I="png";g.extend=function(a,b){return function(c){for(property in c.prototype)this.prototype[property]=c.prototype[property];return this}.apply(a,[b])};g.onAdd=function(){if(!this.v){this.v=true;l(this)}};g.O=function(){};g.draw=function(){}; function k(a){for(var b=0,c;c=a.V[b];b++)a.j.push({url:a.Q+(b+1)+"."+a.P,height:c,width:c})}g=i.prototype;g.u=f("j");g.L=f("a");g.N=f("a");g.C=function(){return this.R||this.b.mapTypes[this.b.getMapTypeId()].maxZoom};g.A=function(a,b){for(var c=0,e=a.length,h=e;h!==0;){h=parseInt(h/10,10);c++}c=Math.min(c,b);return{text:e,index:c}};g.T=d("A");g.B=f("A");g.z=function(a,b){for(var c=0,e;e=a[c];c++)m(this,e);b||this.i()}; function m(a,b){b.setVisible(false);b.setMap(null);b.q=false;b.draggable&&google.maps.event.addListener(b,"dragend",function(){b.q=false;a.m();a.i()});a.a.push(b)}g=i.prototype;g.o=function(a,b){m(this,a);b||this.i()};g.S=function(a){var b=-1;if(this.a.indexOf)b=this.a.indexOf(a);else for(var c=0,e;e=this.a[c];c++)if(e==a)b=c;if(b==-1)return false;this.a.splice(b,1);a.setVisible(false);a.setMap(null);this.m();this.i();return true};g.M=function(){return this.l.length};g.getMap=f("b");g.setMap=d("b"); g.t=f("f");g.U=d("f");function n(a,b){var c=a.getProjection(),e=new google.maps.LatLng(b.getNorthEast().lat(),b.getNorthEast().lng()),h=new google.maps.LatLng(b.getSouthWest().lat(),b.getSouthWest().lng());e=c.fromLatLngToDivPixel(e);e.x+=a.f;e.y-=a.f;h=c.fromLatLngToDivPixel(h);h.x-=a.f;h.y+=a.f;e=c.fromDivPixelToLatLng(e);c=c.fromDivPixelToLatLng(h);b.extend(e);b.extend(c);return b}i.prototype.K=function(){this.m();this.a=[]}; i.prototype.m=function(){for(var a=0,b;b=this.l[a];a++)b.remove();for(a=0;b=this.a[a];a++){b.q=false;b.setMap(null);b.setVisible(false)}this.l=[]};i.prototype.i=function(){l(this)}; function l(a){if(a.v)for(var b=n(a,new google.maps.LatLngBounds(a.b.getBounds().getSouthWest(),a.b.getBounds().getNorthEast())),c=0,e;e=a.a[c];c++){var h=false;if(!e.q&&b.contains(e.getPosition())){for(var q=0,j;j=a.l[q];q++)if(!h&&j.getCenter()&&j.s.contains(e.getPosition())){h=true;j.o(e);break}if(!h){j=new o(a);j.o(e);a.l.push(j)}}}}function o(a){this.h=a;this.b=a.getMap();this.f=a.t();this.d=null;this.a=[];this.s=null;this.k=new p(this,a.u(),a.t())} o.prototype.o=function(a){var b;a:if(this.a.indexOf)b=this.a.indexOf(a)!=-1;else{b=0;for(var c;c=this.a[b];b++)if(c==a){b=true;break a}b=false}if(b)return false;if(!this.d){this.d=a.getPosition();r(this)}if(this.a.length==0){a.setMap(this.b);a.setVisible(true)}else if(this.a.length==1){this.a[0].setMap(null);this.a[0].setVisible(false)}a.q=true;this.a.push(a);if(this.b.getZoom()>this.h.C())for(a=0;b=this.a[a];a++){b.setMap(this.b);b.setVisible(true)}else if(this.a.length<2)s(this.k);else{a=this.h.u().length; b=this.h.B()(this.a,a);this.k.setCenter(this.d);a=this.k;a.w=b;a.ba=b.text;a.X=b.index;if(a.c)a.c.innerHTML=b.text;b=Math.max(0,a.w.index-1);b=Math.min(a.j.length-1,b);b=a.j[b];a.H=b.url;a.g=b.height;a.n=b.width;a.F=b.Z;a.anchor=b.Y;a.G=b.$;this.k.show()}return true};o.prototype.getBounds=function(){r(this);return this.s};o.prototype.remove=function(){this.k.remove();delete this.a};o.prototype.getCenter=f("d");function r(a){a.s=n(a.h,new google.maps.LatLngBounds(a.d,a.d))}o.prototype.getMap=f("b"); function p(a,b,c){a.h.extend(p,google.maps.OverlayView);this.j=b;this.aa=c||0;this.p=a;this.d=null;this.b=a.getMap();this.w=this.c=null;this.r=false;this.setMap(this.b)} p.prototype.onAdd=function(){this.c=document.createElement("DIV");if(this.r){this.c.style.cssText=t(this,u(this,this.d));this.c.innerHTML=this.w.text}this.getPanes().overlayImage.appendChild(this.c);var a=this;google.maps.event.addDomListener(this.c,"click",function(){var b=a.p.h;google.maps.event.trigger(b,"clusterclick",[a.p]);if(b.W){a.b.panTo(a.p.getCenter());a.b.fitBounds(a.p.getBounds())}})}; function u(a,b){var c=a.getProjection().fromLatLngToDivPixel(b);c.x-=parseInt(a.n/2,10);c.y-=parseInt(a.g/2,10);return c}p.prototype.draw=function(){if(this.r){var a=u(this,this.d);this.c.style.top=a.y+"px";this.c.style.left=a.x+"px"}};function s(a){if(a.c)a.c.style.display="none";a.r=false}p.prototype.show=function(){if(this.c){this.c.style.cssText=t(this,u(this,this.d));this.c.style.display=""}this.r=true};p.prototype.remove=function(){this.setMap(null)}; p.prototype.onRemove=function(){if(this.c&&this.c.parentNode){s(this);this.c.parentNode.removeChild(this.c);this.c=null}};p.prototype.setCenter=d("d"); function t(a,b){var c=[];document.all?c.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="'+a.H+'");'):c.push("background:url("+a.H+");");if(typeof a.e==="object"){typeof a.e[0]==="number"&&a.e[0]>0&&a.e[0]<a.g?c.push("height:"+(a.g-a.e[0])+"px; padding-top:"+a.e[0]+"px;"):c.push("height:"+a.g+"px; line-height:"+a.g+"px;");typeof a.e[1]==="number"&&a.e[1]>0&&a.e[1]<a.n?c.push("width:"+(a.n-a.e[1])+"px; padding-left:"+a.e[1]+"px;"):c.push("width:"+a.n+"px; text-align:center;")}else c.push("height:"+ a.g+"px; line-height:"+a.g+"px; width:"+a.n+"px; text-align:center;");c.push("cursor:pointer; top:"+b.y+"px; left:"+b.x+"px; color:"+(a.F?a.F:"black")+"; position:absolute; font-size:"+(a.G?a.G:11)+"px; font-family:Arial,sans-serif; font-weight:bold");return c.join("")}window.MarkerClusterer=i;i.prototype.addMarker=i.prototype.o;i.prototype.addMarkers=i.prototype.z;i.prototype.clearMarkers=i.prototype.K;i.prototype.getCalculator=i.prototype.B;i.prototype.getGridSize=i.prototype.t; i.prototype.getMap=i.prototype.getMap;i.prototype.getMarkers=i.prototype.L;i.prototype.getMaxZoom=i.prototype.C;i.prototype.getStyles=i.prototype.u;i.prototype.getTotalClusters=i.prototype.M;i.prototype.getTotalMarkers=i.prototype.N;i.prototype.redraw=i.prototype.i;i.prototype.removeMarker=i.prototype.S;i.prototype.resetViewport=i.prototype.m;i.prototype.setCalculator=i.prototype.T;i.prototype.setGridSize=i.prototype.U;i.prototype.onAdd=i.prototype.onAdd;i.prototype.draw=i.prototype.draw; i.prototype.idle=i.prototype.O;p.prototype.onAdd=p.prototype.onAdd;p.prototype.draw=p.prototype.draw;p.prototype.onRemove=p.prototype.onRemove;
(function(){var d=true,e=null,g=false;function i(a){return function(b){this[a]=b}}function j(a){return function(){return this[a]}}var k;
function l(a,b,c){this.extend(l,google.maps.OverlayView);this.b=a;this.a=[];this.m=[];this.$=[53,56,66,78,90];this.i=[];this.A=g;c=c||{};this.f=c.gridSize||60;this.V=c.maxZoom||e;this.i=c.styles||[];this.U=c.imagePath||this.O;this.T=c.imageExtension||this.N;this.M=d;if(c.zoomOnClick!=undefined)this.M=c.zoomOnClick;this.p=g;if(c.averageCenter!=undefined)this.p=c.averageCenter;m(this);this.setMap(a);this.I=this.b.getZoom();var f=this;google.maps.event.addListener(this.b,"zoom_changed",function(){var h=
f.b.mapTypes[f.b.getMapTypeId()].maxZoom,o=f.b.getZoom();if(!(o<0||o>h))if(f.I!=o){f.I=f.b.getZoom();f.k()}});google.maps.event.addListener(this.b,"idle",function(){f.h()});b&&b.length&&this.C(b,g)}k=l.prototype;k.O="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m";k.N="png";k.extend=function(a,b){return function(c){for(var f in c.prototype)this.prototype[f]=c.prototype[f];return this}.apply(a,[b])};k.onAdd=function(){if(!this.A){this.A=d;p(this)}};k.draw=function(){};
function m(a){if(!a.i.length)for(var b=0,c;c=a.$[b];b++)a.i.push({url:a.U+(b+1)+"."+a.T,height:c,width:c})}k.z=j("i");k.w=j("a");k.S=function(){return this.a.length};k.H=function(){return this.V||this.b.mapTypes[this.b.getMapTypeId()].maxZoom};k.F=function(a,b){for(var c=0,f=a.length,h=f;h!==0;){h=parseInt(h/10,10);c++}c=Math.min(c,b);return{text:f,index:c}};k.Y=i("F");k.G=j("F");k.C=function(a,b){for(var c=0,f;f=a[c];c++)q(this,f);b||this.h()};
function q(a,b){b.setVisible(g);b.setMap(e);b.r=g;b.draggable&&google.maps.event.addListener(b,"dragend",function(){b.r=g;a.k();a.h()});a.a.push(b)}k.o=function(a,b){q(this,a);b||this.h()};function r(a,b){var c=-1;if(a.a.indexOf)c=a.a.indexOf(b);else for(var f=0,h;h=a.a[f];f++)if(h==b){c=f;break}if(c==-1)return g;a.a.splice(c,1);b.setVisible(g);b.setMap(e);return d}k.W=function(a,b){var c=r(this,a);if(!b&&c){this.k();this.h();return d}else return g};
k.X=function(a,b){for(var c=g,f=0,h;h=a[f];f++){h=r(this,h);c=c||h}if(!b&&c){this.k();this.h();return d}};k.R=function(){return this.m.length};k.getMap=j("b");k.setMap=i("b");k.v=j("f");k.Z=i("f");
k.u=function(a){var b=this.getProjection(),c=new google.maps.LatLng(a.getNorthEast().lat(),a.getNorthEast().lng()),f=new google.maps.LatLng(a.getSouthWest().lat(),a.getSouthWest().lng());c=b.fromLatLngToDivPixel(c);c.x+=this.f;c.y-=this.f;f=b.fromLatLngToDivPixel(f);f.x-=this.f;f.y+=this.f;c=b.fromDivPixelToLatLng(c);b=b.fromDivPixelToLatLng(f);a.extend(c);a.extend(b);return a};k.P=function(){this.k();this.a=[]};
k.k=function(){for(var a=0,b;b=this.m[a];a++)b.remove();for(a=0;b=this.a[a];a++){b.r=g;b.setMap(e);b.setVisible(g)}this.m=[]};k.h=function(){p(this)};function p(a){if(a.A)for(var b=a.u(new google.maps.LatLngBounds(a.b.getBounds().getSouthWest(),a.b.getBounds().getNorthEast())),c=0,f;f=a.a[c];c++){var h=g;if(!f.r&&b.contains(f.getPosition())){for(var o=0,n;n=a.m[o];o++)if(!h&&n.getCenter()&&n.t.contains(f.getPosition())){h=d;n.o(f);break}if(!h){n=new s(a);n.o(f);a.m.push(n)}}}}
function s(a){this.j=a;this.b=a.getMap();this.f=a.v();this.p=a.p;this.d=e;this.a=[];this.t=e;this.l=new t(this,a.z(),a.v())}k=s.prototype;
k.o=function(a){var b;a:if(this.a.indexOf)b=this.a.indexOf(a)!=-1;else{b=0;for(var c;c=this.a[b];b++)if(c==a){b=d;break a}b=g}if(b)return g;if(this.d){if(this.p){c=this.a.length+1;b=(this.d.lat()-a.getPosition().lat())/c;c=(this.d.lng()-a.getPosition().lng())/c;b=this.d.lat()+b;c=this.d.lng()+c;this.d=new google.maps.LatLng(b,c);u(this)}}else{this.d=a.getPosition();u(this)}if(this.a.length==0){a.setMap(this.b);a.setVisible(d)}else if(this.a.length==1){this.a[0].setMap(e);this.a[0].setVisible(g)}a.r=
d;this.a.push(a);if(this.b.getZoom()>this.j.H())for(a=0;b=this.a[a];a++){b.setMap(this.b);b.setVisible(d)}else if(this.a.length<2)v(this.l);else{b=this.j.G()(this.a,this.j.z().length);this.l.setCenter(this.d);a=this.l;a.B=b;a.ca=b.text;a.aa=b.index;if(a.c)a.c.innerHTML=b.text;b=Math.max(0,a.B.index-1);b=Math.min(a.i.length-1,b);b=a.i[b];a.L=b.url;a.g=b.height;a.n=b.width;a.J=b.textColor;a.e=b.anchor;a.K=b.textSize;a.D=b.backgroundPosition;this.l.show()}return d};k.getBounds=function(){u(this);return this.t};
k.remove=function(){this.l.remove();this.a.length=0;delete this.a};k.Q=function(){return this.a.length};k.w=j("a");k.getCenter=j("d");function u(a){a.t=a.j.u(new google.maps.LatLngBounds(a.d,a.d))}k.getMap=j("b");function t(a,b,c){a.j.extend(t,google.maps.OverlayView);this.i=b;this.ba=c||0;this.q=a;this.d=e;this.b=a.getMap();this.B=this.c=e;this.s=g;this.setMap(this.b)}k=t.prototype;
k.onAdd=function(){this.c=document.createElement("DIV");if(this.s){this.c.style.cssText=w(this,x(this,this.d));this.c.innerHTML=this.B.text}this.getPanes().overlayImage.appendChild(this.c);var a=this;google.maps.event.addDomListener(this.c,"click",function(){var b=a.q.j;google.maps.event.trigger(b,"clusterclick",a.q);if(b.M){a.b.panTo(a.q.getCenter());a.b.fitBounds(a.q.getBounds())}})};
function x(a,b){var c=a.getProjection().fromLatLngToDivPixel(b);c.x-=parseInt(a.n/2,10);c.y-=parseInt(a.g/2,10);return c}k.draw=function(){if(this.s){var a=x(this,this.d);this.c.style.top=a.y+"px";this.c.style.left=a.x+"px"}};function v(a){if(a.c)a.c.style.display="none";a.s=g}k.show=function(){if(this.c){this.c.style.cssText=w(this,x(this,this.d));this.c.style.display=""}this.s=d};k.remove=function(){this.setMap(e)};
k.onRemove=function(){if(this.c&&this.c.parentNode){v(this);this.c.parentNode.removeChild(this.c);this.c=e}};k.setCenter=i("d");
function w(a,b){var c=[];if(document.all)c.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="'+a.L+'");');else{c.push("background-image:url("+a.L+");");c.push("background-position:"+(a.D?a.D:"0 0")+";")}if(typeof a.e==="object"){typeof a.e[0]==="number"&&a.e[0]>0&&a.e[0]<a.g?c.push("height:"+(a.g-a.e[0])+"px; padding-top:"+a.e[0]+"px;"):c.push("height:"+a.g+"px; line-height:"+a.g+"px;");typeof a.e[1]==="number"&&a.e[1]>0&&a.e[1]<a.n?c.push("width:"+(a.n-a.e[1])+
"px; padding-left:"+a.e[1]+"px;"):c.push("width:"+a.n+"px; text-align:center;")}else c.push("height:"+a.g+"px; line-height:"+a.g+"px; width:"+a.n+"px; text-align:center;");c.push("cursor:pointer; top:"+b.y+"px; left:"+b.x+"px; color:"+(a.J?a.J:"black")+"; position:absolute; font-size:"+(a.K?a.K:11)+"px; font-family:Arial,sans-serif; font-weight:bold");return c.join("")}window.MarkerClusterer=l;l.prototype.addMarker=l.prototype.o;l.prototype.addMarkers=l.prototype.C;l.prototype.clearMarkers=l.prototype.P;
l.prototype.getCalculator=l.prototype.G;l.prototype.getGridSize=l.prototype.v;l.prototype.getExtendedBounds=l.prototype.u;l.prototype.getMap=l.prototype.getMap;l.prototype.getMarkers=l.prototype.w;l.prototype.getMaxZoom=l.prototype.H;l.prototype.getStyles=l.prototype.z;l.prototype.getTotalClusters=l.prototype.R;l.prototype.getTotalMarkers=l.prototype.S;l.prototype.redraw=l.prototype.h;l.prototype.removeMarker=l.prototype.W;l.prototype.removeMarkers=l.prototype.X;l.prototype.resetViewport=l.prototype.k;
l.prototype.setCalculator=l.prototype.Y;l.prototype.setGridSize=l.prototype.Z;l.prototype.onAdd=l.prototype.onAdd;l.prototype.draw=l.prototype.draw;s.prototype.getCenter=s.prototype.getCenter;s.prototype.getSize=s.prototype.Q;s.prototype.getMarkers=s.prototype.w;t.prototype.onAdd=t.prototype.onAdd;t.prototype.draw=t.prototype.draw;t.prototype.onRemove=t.prototype.onRemove;
})();
/trunk/widget/modules/carto/squelettes/markerclusterer.js
1,27 → 1,22
// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js
// ==/ClosureCompiler==
 
/**
* @name MarkerClusterer
* @version 1.0
* @author Xiaoxi Wu
* @copyright (c) 2009 Xiaoxi Wu
* @name MarkerClusterer for Google Maps v3
* @version version 1.0
* @author Luke Mahe
* @fileoverview
* This javascript library creates and manages per-zoom-level
* clusters for large amounts of markers (hundreds or thousands).
* This library was inspired by the <a href="http://www.maptimize.com">
* Maptimize</a> hosted clustering solution.
* <br /><br/>
* <b>How it works</b>:<br/>
* The <code>MarkerClusterer</code> will group markers into clusters according to
* their distance from a cluster's center. When a marker is added,
* the marker cluster will find a position in all the clusters, and
* if it fails to find one, it will create a new cluster with the marker.
* The number of markers in a cluster will be displayed
* on the cluster marker. When the map viewport changes,
* <code>MarkerClusterer</code> will destroy the clusters in the viewport
* and regroup them into new clusters.
*
* The library creates and manages per-zoom-level clusters for large amounts of
* markers.
* <br/>
* This is a v3 implementation of the
* <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
* >v2 MarkerClusterer</a>.
*/
 
/*
/**
* 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
37,699 → 32,1157
 
 
/**
* @name MarkerClustererOptions
* @class This class represents optional arguments to the {@link MarkerClusterer}
* constructor.
* @property {Number} [maxZoom] The max zoom level monitored by a
* marker cluster. If not given, the marker cluster assumes the maximum map
* zoom level. When maxZoom is reached or exceeded all markers will be shown
* without cluster.
* @property {Number} [gridSize=60] The grid size of a cluster in pixel. Each
* cluster will be a square. If you want the algorithm to run faster, you can set
* this value larger.
* @property {Array of MarkerStyleOptions} [styles]
* Custom styles for the cluster markers.
* The array should be ordered according to increasing cluster size,
* with the style for the smallest clusters first, and the style for the
* largest clusters last.
*/
 
/**
* @name MarkerStyleOptions
* @class An array of these is passed into the {@link MarkerClustererOptions}
* styles option.
* @property {String} [url] Image url.
* @property {Number} [height] Image height.
* @property {Number} [height] Image width.
* @property {Array of Number} [opt_anchor] Anchor for label text, like [24, 12].
* If not set, the text will align center and middle.
* @property {String} [opt_textColor="black"] Text color.
*/
 
/**
* Creates a new MarkerClusterer to cluster markers on the map.
* A Marker Clusterer that clusters markers.
*
* @param {google.maps.Map} map The Google map to attach to.
* @param {Array.<google.maps.Marker>=} opt_markers Optional markers to add to
* the cluster.
* @param {Object=} opt_options support the following options:
* 'gridSize': (number) The grid size of a cluster in pixels.
* 'maxZoom': (number) The maximum zoom level that a marker can be part of a
* cluster.
* 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a
* cluster is to zoom into it.
* 'averageCenter': (boolean) Wether the center of each cluster should be
* the average of all markers in the cluster.
* 'styles': (object) An object that has style properties:
* 'url': (string) The image url.
* 'height': (number) The image height.
* 'width': (number) The image width.
* 'anchor': (Array) The anchor position of the label text.
* 'textColor': (string) The text color.
* 'textSize': (number) The text size.
* 'backgroundPosition': (string) The position of the backgound x, y.
* @constructor
* @param {GMap2} map The map that the markers should be added to.
* @param {Array of GMarker} opt_markers Initial set of markers to be clustered.
* @param {MarkerClustererOptions} opt_opts A container for optional arguments.
* @extends google.maps.OverlayView
*/
function MarkerClusterer(map, opt_markers, opt_opts) {
// private members
var clusters_ = [];
var map_ = map;
var maxZoom_ = null;
var me_ = this;
var gridSize_ = 60;
var sizes = [53, 56, 66, 78, 90];
var styles_ = [];
var leftMarkers_ = [];
var mcfn_ = null;
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);
this.map_ = map;
 
var i = 0;
for (i = 1; i <= 5; ++i) {
styles_.push({
'url': "http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/images/m" + i + ".png",
'height': sizes[i - 1],
'width': sizes[i - 1]
});
}
 
if (typeof opt_opts === "object" && opt_opts !== null) {
if (typeof opt_opts.gridSize === "number" && opt_opts.gridSize > 0) {
gridSize_ = opt_opts.gridSize;
}
if (typeof opt_opts.maxZoom === "number") {
maxZoom_ = opt_opts.maxZoom;
}
if (typeof opt_opts.styles === "object" && opt_opts.styles !== null && opt_opts.styles.length !== 0) {
styles_ = opt_opts.styles;
}
}
 
/**
* When we add a marker, the marker may not in the viewport of map, then we don't deal with it, instead
* we add the marker into a array called leftMarkers_. When we reset MarkerClusterer we should add the
* leftMarkers_ into MarkerClusterer.
*/
function addLeftMarkers_() {
if (leftMarkers_.length === 0) {
return;
}
var leftMarkers = [];
for (i = 0; i < leftMarkers_.length; ++i) {
me_.addMarker(leftMarkers_[i], true, null, null, true);
}
leftMarkers_ = leftMarkers;
}
 
/**
* Get cluster marker images of this marker cluster. Mostly used by {@link Cluster}
* @type {Array.<google.maps.Marker>}
* @private
* @return {Array of String}
*/
this.getStyles_ = function () {
return styles_;
};
this.markers_ = [];
 
/**
* Remove all markers from MarkerClusterer.
* @type {Array.<Cluster>}
*/
this.clearMarkers = function () {
for (var i = 0; i < clusters_.length; ++i) {
if (typeof clusters_[i] !== "undefined" && clusters_[i] !== null) {
clusters_[i].clearMarkers();
}
}
clusters_ = [];
leftMarkers_ = [];
GEvent.removeListener(mcfn_);
};
this.clusters_ = [];
 
this.sizes = [53, 56, 66, 78, 90];
 
/**
* Check a marker, whether it is in current map viewport.
* @private
* @return {Boolean} if it is in current map viewport
*/
function isMarkerInViewport_(marker) {
return map_.getBounds().containsLatLng(marker.getLatLng());
}
this.styles_ = [];
 
/**
* When reset MarkerClusterer, there will be some markers get out of its cluster.
* These markers should be add to new clusters.
* @param {Array of GMarker} markers Markers to add.
* @type {boolean}
* @private
*/
function reAddMarkers_(markers) {
var len = markers.length;
var clusters = [];
for (var i = len - 1; i >= 0; --i) {
me_.addMarker(markers[i].marker, true, markers[i].isAdded, clusters, true);
}
addLeftMarkers_();
}
this.ready_ = false;
 
var options = opt_options || {};
 
/**
* Add a marker.
* @type {number}
* @private
* @param {GMarker} marker Marker you want to add
* @param {Boolean} opt_isNodraw Whether redraw the cluster contained the marker
* @param {Boolean} opt_isAdded Whether the marker is added to map. Never use it.
* @param {Array of Cluster} opt_clusters Provide a list of clusters, the marker
* cluster will only check these cluster where the marker should join.
*/
this.addMarker = function (marker, opt_isNodraw, opt_isAdded, opt_clusters, opt_isNoCheck) {
if (opt_isNoCheck !== true) {
if (!isMarkerInViewport_(marker)) {
leftMarkers_.push(marker);
return;
}
}
this.gridSize_ = options['gridSize'] || 60;
 
var isAdded = opt_isAdded;
var clusters = opt_clusters;
var pos = map_.fromLatLngToDivPixel(marker.getLatLng());
 
if (typeof isAdded !== "boolean") {
isAdded = false;
}
if (typeof clusters !== "object" || clusters === null) {
clusters = clusters_;
}
 
var length = clusters.length;
var cluster = null;
for (var i = length - 1; i >= 0; i--) {
cluster = clusters[i];
var center = cluster.getCenter();
if (center === null) {
continue;
}
center = map_.fromLatLngToDivPixel(center);
 
// Found a cluster which contains the marker.
if (pos.x >= center.x - gridSize_ && pos.x <= center.x + gridSize_ &&
pos.y >= center.y - gridSize_ && pos.y <= center.y + gridSize_) {
cluster.addMarker({
'isAdded': isAdded,
'marker': marker
});
if (!opt_isNodraw) {
cluster.redraw_();
}
return;
}
}
 
// No cluster contain the marker, create a new cluster.
cluster = new Cluster(this, map);
cluster.addMarker({
'isAdded': isAdded,
'marker': marker
});
if (!opt_isNodraw) {
cluster.redraw_();
}
 
// Add this cluster both in clusters provided and clusters_
clusters.push(cluster);
if (clusters !== clusters_) {
clusters_.push(cluster);
}
};
 
/**
* Remove a marker.
*
* @param {GMarker} marker The marker you want to remove.
* @type {?number}
* @private
*/
this.maxZoom_ = options['maxZoom'] || null;
 
this.removeMarker = function (marker) {
for (var i = 0; i < clusters_.length; ++i) {
if (clusters_[i].remove(marker)) {
clusters_[i].redraw_();
return;
}
}
};
this.styles_ = options['styles'] || [];
 
/**
* Redraw all clusters in viewport.
* @type {string}
* @private
*/
this.redraw_ = function () {
var clusters = this.getClustersInViewport_();
for (var i = 0; i < clusters.length; ++i) {
clusters[i].redraw_(true);
}
};
this.imagePath_ = options['imagePath'] ||
this.MARKER_CLUSTER_IMAGE_PATH_;
 
/**
* Get all clusters in viewport.
* @return {Array of Cluster}
* @type {string}
* @private
*/
this.getClustersInViewport_ = function () {
var clusters = [];
var curBounds = map_.getBounds();
for (var i = 0; i < clusters_.length; i ++) {
if (clusters_[i].isInBounds(curBounds)) {
clusters.push(clusters_[i]);
}
}
return clusters;
};
this.imageExtension_ = options['imageExtension'] ||
this.MARKER_CLUSTER_IMAGE_EXTENSION_;
 
/**
* Get max zoom level.
* @type {boolean}
* @private
* @return {Number}
*/
this.getMaxZoom_ = function () {
return maxZoom_;
};
this.zoomOnClick_ = true;
 
if (options['zoomOnClick'] != undefined) {
this.zoomOnClick_ = options['zoomOnClick'];
}
 
/**
* Get map object.
* @type {boolean}
* @private
* @return {GMap2}
*/
this.getMap_ = function () {
return map_;
};
this.averageCenter_ = false;
 
if (options['averageCenter'] != undefined) {
this.averageCenter_ = options['averageCenter'];
}
 
this.setupStyles_();
 
this.setMap(map);
 
/**
* Get grid size
* @type {number}
* @private
* @return {Number}
*/
this.getGridSize_ = function () {
return gridSize_;
};
this.prevZoom_ = this.map_.getZoom();
 
/**
* Get total number of markers.
* @return {Number}
*/
this.getTotalMarkers = function () {
var result = 0;
for (var i = 0; i < clusters_.length; ++i) {
result += clusters_[i].getTotalMarkers();
// Add the map event listeners
var that = this;
google.maps.event.addListener(this.map_, 'zoom_changed', function() {
var maxZoom = that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom;
var zoom = that.map_.getZoom();
if (zoom < 0 || zoom > maxZoom) {
return;
}
return result;
};
 
/**
* Get total number of clusters.
* @return {int}
*/
this.getTotalClusters = function () {
return clusters_.length;
};
if (that.prevZoom_ != zoom) {
that.prevZoom_ = that.map_.getZoom();
that.resetViewport();
}
});
 
/**
* Collect all markers of clusters in viewport and regroup them.
*/
this.resetViewport = function () {
var clusters = this.getClustersInViewport_();
var tmpMarkers = [];
var removed = 0;
google.maps.event.addListener(this.map_, 'idle', function() {
that.redraw();
});
 
for (var i = 0; i < clusters.length; ++i) {
var cluster = clusters[i];
var oldZoom = cluster.getCurrentZoom();
if (oldZoom === null) {
continue;
}
var curZoom = map_.getZoom();
if (curZoom !== oldZoom) {
// Finally, add the markers
if (opt_markers && opt_markers.length) {
this.addMarkers(opt_markers, false);
}
}
 
// If the cluster zoom level changed then destroy the cluster
// and collect its markers.
var mks = cluster.getMarkers();
for (var j = 0; j < mks.length; ++j) {
var newMarker = {
'isAdded': false,
'marker': mks[j].marker
};
tmpMarkers.push(newMarker);
}
cluster.clearMarkers();
removed++;
for (j = 0; j < clusters_.length; ++j) {
if (cluster === clusters_[j]) {
clusters_.splice(j, 1);
}
}
}
}
 
// Add the markers collected into marker cluster to reset
reAddMarkers_(tmpMarkers);
this.redraw_();
};
/**
* The marker cluster image path.
*
* @type {string}
* @private
*/
MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ =
'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/' +
'images/m';
 
 
/**
* Add a set of markers.
*
* @param {Array of GMarker} markers The markers you want to add.
*/
this.addMarkers = function (markers) {
for (var i = 0; i < markers.length; ++i) {
this.addMarker(markers[i], true);
/**
* The marker cluster image path.
*
* @type {string}
* @private
*/
MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png';
 
 
/**
* Extends a objects prototype by anothers.
*
* @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) {
for (var property in object.prototype) {
this.prototype[property] = object.prototype[property];
}
this.redraw_();
};
return this;
}).apply(obj1, [obj2]);
};
 
// initialize
if (typeof opt_markers === "object" && opt_markers !== null) {
this.addMarkers(opt_markers);
}
 
// when map move end, regroup.
mcfn_ = GEvent.addListener(map_, "moveend", function () {
me_.resetViewport();
});
}
/**
* Implementaion of the interface method.
* @ignore
*/
MarkerClusterer.prototype.onAdd = function() {
this.setReady_(true);
};
 
/**
* Create a cluster to collect markers.
* A cluster includes some markers which are in a block of area.
* If there are more than one markers in cluster, the cluster
* will create a {@link ClusterMarker_} and show the total number
* of markers in cluster.
* Implementaion of the interface method.
* @ignore
*/
MarkerClusterer.prototype.draw = function() {};
 
/**
* Sets up the styles object.
*
* @constructor
* @private
* @param {MarkerClusterer} markerClusterer The marker cluster object
*/
function Cluster(markerClusterer) {
var center_ = null;
var markers_ = [];
var markerClusterer_ = markerClusterer;
var map_ = markerClusterer.getMap_();
var clusterMarker_ = null;
var zoom_ = map_.getZoom();
MarkerClusterer.prototype.setupStyles_ = function() {
if (this.styles_.length) {
return;
}
 
/**
* Get markers of this cluster.
*
* @return {Array of GMarker}
*/
this.getMarkers = function () {
return markers_;
};
for (var i = 0, size; size = this.sizes[i]; i++) {
this.styles_.push({
url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_,
height: size,
width: size
});
}
};
 
/**
* If this cluster intersects certain bounds.
*
* @param {GLatLngBounds} bounds A bounds to test
* @return {Boolean} Is this cluster intersects the bounds
*/
this.isInBounds = function (bounds) {
if (center_ === null) {
return false;
}
 
if (!bounds) {
bounds = map_.getBounds();
}
var sw = map_.fromLatLngToDivPixel(bounds.getSouthWest());
var ne = map_.fromLatLngToDivPixel(bounds.getNorthEast());
/**
* Sets the styles.
*
* @param {Object} styles The style to set.
*/
MarkerClusterer.prototype.setStyles = function(styles) {
this.styles_ = styles;
};
 
var centerxy = map_.fromLatLngToDivPixel(center_);
var inViewport = true;
var gridSize = markerClusterer.getGridSize_();
if (zoom_ !== map_.getZoom()) {
var dl = map_.getZoom() - zoom_;
gridSize = Math.pow(2, dl) * gridSize;
}
if (ne.x !== sw.x && (centerxy.x + gridSize < sw.x || centerxy.x - gridSize > ne.x)) {
inViewport = false;
}
if (inViewport && (centerxy.y + gridSize < ne.y || centerxy.y - gridSize > sw.y)) {
inViewport = false;
}
return inViewport;
};
 
/**
* Get cluster center.
*
* @return {GLatLng}
*/
this.getCenter = function () {
return center_;
};
/**
* Gets the styles.
*
* @return {Object} The styles object.
*/
MarkerClusterer.prototype.getStyles = function() {
return this.styles_;
};
 
/**
* Add a marker.
*
* @param {Object} marker An object of marker you want to add:
* {Boolean} isAdded If the marker is added on map.
* {GMarker} marker The marker you want to add.
*/
this.addMarker = function (marker) {
if (center_ === null) {
/*var pos = marker['marker'].getLatLng();
pos = map.fromLatLngToContainerPixel(pos);
pos.x = parseInt(pos.x - pos.x % (GRIDWIDTH * 2) + GRIDWIDTH);
pos.y = parseInt(pos.y - pos.y % (GRIDWIDTH * 2) + GRIDWIDTH);
center = map.fromContainerPixelToLatLng(pos);*/
center_ = marker.marker.getLatLng();
}
markers_.push(marker);
 
/**
* Whether zoom on click is set.
*
* @return {boolean} True if zoomOnClick_ is set.
*/
MarkerClusterer.prototype.isZoomOnClick = function() {
return this.zoomOnClick_;
};
 
/**
* Whether average center is set.
*
* @return {boolean} True if averageCenter_ is set.
*/
MarkerClusterer.prototype.isAverageCenter = function() {
return this.averageCenter_;
};
 
 
/**
* Returns the array of markers in the clusterer.
*
* @return {Array.<google.maps.Marker>} The markers.
*/
MarkerClusterer.prototype.getMarkers = function() {
return this.markers_;
};
 
 
/**
* Returns the number of markers in the clusterer
*
* @return {Number} The number of markers.
*/
MarkerClusterer.prototype.getTotalMarkers = function() {
return this.markers_.length;
};
 
 
/**
* Sets the max zoom for the clusterer.
*
* @param {number} maxZoom The max zoom level.
*/
MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
this.maxZoom_ = maxZoom;
};
 
 
/**
* Gets the max zoom for the clusterer.
*
* @return {number} The max zoom level.
*/
MarkerClusterer.prototype.getMaxZoom = function() {
return this.maxZoom_ || this.map_.mapTypes[this.map_.getMapTypeId()].maxZoom;
};
 
 
/**
* The function for calculating the cluster icon image.
*
* @param {Array.<google.maps.Marker>} markers The markers in the clusterer.
* @param {number} numStyles The number of styles available.
* @return {Object} A object properties: 'text' (string) and 'index' (number).
* @private
*/
MarkerClusterer.prototype.calculator_ = function(markers, numStyles) {
var index = 0;
var count = markers.length;
var dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index++;
}
 
index = Math.min(index, numStyles);
return {
text: count,
index: index
};
};
 
/**
* Remove a marker from cluster.
*
* @param {GMarker} marker The marker you want to remove.
* @return {Boolean} Whether find the marker to be removed.
*/
this.removeMarker = function (marker) {
for (var i = 0; i < markers_.length; ++i) {
if (marker === markers_[i].marker) {
if (markers_[i].isAdded) {
map_.removeOverlay(markers_[i].marker);
}
markers_.splice(i, 1);
return true;
 
/**
* Set the calculator function.
*
* @param {function(Array, number)} calculator The function to set as the
* calculator. The function should return a object properties:
* 'text' (string) and 'index' (number).
*
*/
MarkerClusterer.prototype.setCalculator = function(calculator) {
this.calculator_ = calculator;
};
 
 
/**
* Get the calculator function.
*
* @return {function(Array, number)} the calculator function.
*/
MarkerClusterer.prototype.getCalculator = function() {
return this.calculator_;
};
 
 
/**
* Add an array of markers to the clusterer.
*
* @param {Array.<google.maps.Marker>} markers The markers to add.
* @param {boolean=} opt_nodraw Whether to redraw the clusters.
*/
MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) {
for (var i = 0, marker; marker = markers[i]; i++) {
this.pushMarkerTo_(marker);
}
if (!opt_nodraw) {
this.redraw();
}
};
 
 
/**
* Pushes a marker to the clusterer.
*
* @param {google.maps.Marker} marker The marker to add.
* @private
*/
MarkerClusterer.prototype.pushMarkerTo_ = function(marker) {
marker.setVisible(false);
marker.setMap(null);
marker.isAdded = false;
if (marker['draggable']) {
// If the marker is draggable add a listener so we update the clusters on
// the drag end.
var that = this;
google.maps.event.addListener(marker, 'dragend', function() {
marker.isAdded = false;
that.resetViewport();
that.redraw();
});
}
this.markers_.push(marker);
};
 
 
/**
* Adds a marker to the clusterer and redraws if needed.
*
* @param {google.maps.Marker} marker The marker to add.
* @param {boolean=} opt_nodraw Whether to redraw the clusters.
*/
MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) {
this.pushMarkerTo_(marker);
if (!opt_nodraw) {
this.redraw();
}
};
 
 
/**
* 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
* @private
*/
MarkerClusterer.prototype.removeMarker_ = function(marker) {
var index = -1;
if (this.markers_.indexOf) {
index = this.markers_.indexOf(marker);
} else {
for (var i = 0, m; m = this.markers_[i]; i++) {
if (m == marker) {
index = i;
break;
}
}
}
 
if (index == -1) {
// Marker is not in our list of markers.
return false;
};
}
 
/**
* Get current zoom level of this cluster.
* Note: the cluster zoom level and map zoom level not always the same.
*
* @return {Number}
*/
this.getCurrentZoom = function () {
return zoom_;
};
this.markers_.splice(index, 1);
marker.setVisible(false);
marker.setMap(null);
 
/**
* Redraw a cluster.
* @private
* @param {Boolean} isForce If redraw by force, no matter if the cluster is
* in viewport.
*/
this.redraw_ = function (isForce) {
if (!isForce && !this.isInBounds()) {
return;
}
return true;
};
 
// Set cluster zoom level.
zoom_ = map_.getZoom();
var i = 0;
var mz = markerClusterer.getMaxZoom_();
if (mz === null) {
mz = map_.getCurrentMapType().getMaximumResolution();
}
if (zoom_ >= mz || this.getTotalMarkers() === 1) {
 
// If current zoom level is beyond the max zoom level or the cluster
// have only one marker, the marker(s) in cluster will be showed on map.
for (i = 0; i < markers_.length; ++i) {
if (markers_[i].isAdded) {
if (markers_[i].marker.isHidden()) {
markers_[i].marker.show();
}
} else {
map_.addOverlay(markers_[i].marker);
markers_[i].isAdded = true;
/**
* Remove a marker from the cluster.
*
* @param {google.maps.Marker} marker The marker to remove.
* @param {boolean=} opt_nodraw Optional boolean to force no redraw.
* @return {boolean} True if the marker was removed.
*/
MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) {
var removed = this.removeMarker_(marker);
 
if (!opt_nodraw && removed) {
this.resetViewport();
this.redraw();
return true;
} else {
return false;
}
};
 
 
/**
* Removes an array of markers from the cluster.
*
* @param {Array.<google.maps.Marker>} markers The markers to remove.
* @param {boolean=} opt_nodraw Optional boolean to force no redraw.
*/
MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) {
var removed = false;
 
for (var i = 0, marker; marker = markers[i]; i++) {
var r = this.removeMarker_(marker);
removed = removed || r;
}
 
if (!opt_nodraw && removed) {
this.resetViewport();
this.redraw();
return true;
}
};
 
 
/**
* Sets the clusterer's ready state.
*
* @param {boolean} ready The state.
* @private
*/
MarkerClusterer.prototype.setReady_ = function(ready) {
if (!this.ready_) {
this.ready_ = ready;
this.createClusters_();
}
};
 
 
/**
* Returns the number of clusters in the clusterer.
*
* @return {number} The number of clusters.
*/
MarkerClusterer.prototype.getTotalClusters = function() {
return this.clusters_.length;
};
 
 
/**
* Returns the google map that the clusterer is associated with.
*
* @return {google.maps.Map} The map.
*/
MarkerClusterer.prototype.getMap = function() {
return this.map_;
};
 
 
/**
* Sets the google map that the clusterer is associated with.
*
* @param {google.maps.Map} map The map.
*/
MarkerClusterer.prototype.setMap = function(map) {
this.map_ = map;
};
 
 
/**
* Returns the size of the grid.
*
* @return {number} The grid size.
*/
MarkerClusterer.prototype.getGridSize = function() {
return this.gridSize_;
};
 
 
/**
* Returns the size of the grid.
*
* @param {number} size The grid size.
*/
MarkerClusterer.prototype.setGridSize = function(size) {
this.gridSize_ = size;
};
 
 
/**
* Extends a bounds object by the grid size.
*
* @param {google.maps.LatLngBounds} bounds The bounds to extend.
* @return {google.maps.LatLngBounds} The extended bounds.
*/
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;
};
 
 
/**
* Determins 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.
* @private
*/
MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) {
return bounds.contains(marker.getPosition());
};
 
 
/**
* Clears all clusters and markers from the clusterer.
*/
MarkerClusterer.prototype.clearMarkers = function() {
this.resetViewport();
 
// Set the markers a empty array.
this.markers_ = [];
};
 
 
/**
* Clears all existing clusters and recreates them.
*/
MarkerClusterer.prototype.resetViewport = function() {
// Remove all the clusters
for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
cluster.remove();
}
 
// Reset the markers to not be added and to be invisible.
for (var i = 0, marker; marker = this.markers_[i]; i++) {
marker.isAdded = false;
marker.setMap(null);
marker.setVisible(false);
}
 
this.clusters_ = [];
};
 
 
/**
* Redraws the clusters.
*/
MarkerClusterer.prototype.redraw = function() {
this.createClusters_();
};
 
 
/**
* Creates the clusters.
*
* @private
*/
MarkerClusterer.prototype.createClusters_ = function() {
if (!this.ready_) {
return;
}
 
// Get our current map view bounds.
// Create a new bounds object so we don't affect the map.
var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(),
this.map_.getBounds().getNorthEast());
var bounds = this.getExtendedBounds(mapBounds);
 
for (var i = 0, marker; marker = this.markers_[i]; i++) {
var added = false;
if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
for (var j = 0, cluster; cluster = this.clusters_[j]; j++) {
if (!added && cluster.getCenter() &&
cluster.isMarkerInClusterBounds(marker)) {
added = true;
cluster.addMarker(marker);
break;
}
}
if (clusterMarker_ !== null) {
clusterMarker_.hide();
 
if (!added) {
// Create a new cluster.
var cluster = new Cluster(this);
cluster.addMarker(marker);
this.clusters_.push(cluster);
}
} else {
// Else add a cluster marker on map to show the number of markers in
// this cluster.
for (i = 0; i < markers_.length; ++i) {
if (markers_[i].isAdded && (!markers_[i].marker.isHidden())) {
markers_[i].marker.hide();
}
}
if (clusterMarker_ === null) {
clusterMarker_ = new ClusterMarker_(center_, this.getTotalMarkers(), markerClusterer_.getStyles_(), markerClusterer_.getGridSize_());
map_.addOverlay(clusterMarker_);
} else {
if (clusterMarker_.isHidden()) {
clusterMarker_.show();
}
clusterMarker_.redraw(true);
}
}
};
}
};
 
/**
* Remove all the markers from this cluster.
*/
this.clearMarkers = function () {
if (clusterMarker_ !== null) {
map_.removeOverlay(clusterMarker_);
}
for (var i = 0; i < markers_.length; ++i) {
if (markers_[i].isAdded) {
map_.removeOverlay(markers_[i].marker);
 
/**
* A cluster that contains markers.
*
* @param {MarkerClusterer} markerClusterer The markerclusterer that this
* cluster is associated with.
* @constructor
* @ignore
*/
function Cluster(markerClusterer) {
this.markerClusterer_ = markerClusterer;
this.map_ = markerClusterer.getMap();
this.gridSize_ = markerClusterer.getGridSize();
this.averageCenter_ = markerClusterer.isAverageCenter();
this.center_ = null;
this.markers_ = [];
this.bounds_ = null;
this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(),
markerClusterer.getGridSize());
}
 
/**
* Determins if a marker is already added to the cluster.
*
* @param {google.maps.Marker} marker The marker to check.
* @return {boolean} True if the marker is already added.
*/
Cluster.prototype.isMarkerAlreadyAdded = function(marker) {
if (this.markers_.indexOf) {
return this.markers_.indexOf(marker) != -1;
} else {
for (var i = 0, m; m = this.markers_[i]; i++) {
if (m == marker) {
return true;
}
}
markers_ = [];
};
}
return false;
};
 
/**
* Get number of markers.
* @return {Number}
*/
this.getTotalMarkers = function () {
return markers_.length;
};
}
 
/**
* ClusterMarker_ creates a marker that shows the number of markers that
* a cluster contains.
* Add a marker the cluster.
*
* @constructor
* @param {google.maps.Marker} marker The marker to add.
* @return {boolean} True if the marker was added.
*/
Cluster.prototype.addMarker = function(marker) {
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 latDiff = (this.center_.lat() - marker.getPosition().lat()) / l;
var lngDiff = (this.center_.lng() - marker.getPosition().lng()) / l;
var lat = this.center_.lat() + latDiff;
var lng = this.center_.lng() + lngDiff;
this.center_ = new google.maps.LatLng(lat, lng);
this.calculateBounds_();
}
}
 
 
if (this.markers_.length == 0) {
// Only 1 marker in this cluster so show the marker.
marker.setMap(this.map_);
marker.setVisible(true);
} else if (this.markers_.length == 1) {
// Hide the 1 marker that was showing.
this.markers_[0].setMap(null);
this.markers_[0].setVisible(false);
}
 
marker.isAdded = true;
this.markers_.push(marker);
 
this.updateIcon();
return true;
};
 
 
/**
* Returns the marker clusterer that the cluster is associated with.
*
* @return {MarkerClusterer} The associated marker clusterer.
*/
Cluster.prototype.getMarkerClusterer = function() {
return this.markerClusterer_;
};
 
 
/**
* Returns the bounds of the cluster.
*
* @return {google.maps.LatLngBounds} the cluster bounds.
*/
Cluster.prototype.getBounds = function() {
this.calculateBounds_();
return this.bounds_;
};
 
 
/**
* Removes the cluster
*/
Cluster.prototype.remove = function() {
this.clusterIcon_.remove();
this.markers_.length = 0;
delete this.markers_;
};
 
 
/**
* Returns the center of the cluster.
*
* @return {number} The cluster center.
*/
Cluster.prototype.getSize = function() {
return this.markers_.length;
};
 
 
/**
* Returns the center of the cluster.
*
* @return {Array.<google.maps.Marker>} The cluster center.
*/
Cluster.prototype.getMarkers = function() {
return this.markers_;
};
 
 
/**
* Returns the center of the cluster.
*
* @return {google.maps.LatLng} The cluster center.
*/
Cluster.prototype.getCenter = function() {
return this.center_;
};
 
 
/**
* Calculated the bounds of the cluster with the grid.
*
* @private
* @param {GLatLng} latlng Marker's lat and lng.
* @param {Number} count Number to show.
* @param {Array of Object} styles The image list to be showed:
* {String} url Image url.
* {Number} height Image height.
* {Number} width Image width.
* {Array of Number} anchor Text anchor of image left and top.
* {String} textColor text color.
* @param {Number} padding Padding of marker center.
*/
function ClusterMarker_(latlng, count, styles, padding) {
var index = 0;
var dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index ++;
Cluster.prototype.calculateBounds_ = function() {
var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
};
 
 
/**
* Determines if a marker lies in the clusters bounds.
*
* @param {google.maps.Marker} marker The marker to check.
* @return {boolean} True if the marker lies in the bounds.
*/
Cluster.prototype.isMarkerInClusterBounds = function(marker) {
return this.bounds_.contains(marker.getPosition());
};
 
 
/**
* Returns the map that the cluster is associated with.
*
* @return {google.maps.Map} The map.
*/
Cluster.prototype.getMap = function() {
return this.map_;
};
 
 
/**
* Updates the cluster icon
*/
Cluster.prototype.updateIcon = function() {
var zoom = this.map_.getZoom();
var mz = this.markerClusterer_.getMaxZoom();
 
if (zoom > mz) {
// The zoom is greater than our max zoom so show all the markers in cluster.
for (var i = 0, marker; marker = this.markers_[i]; i++) {
marker.setMap(this.map_);
marker.setVisible(true);
}
return;
}
 
if (styles.length < index) {
index = styles.length;
if (this.markers_.length < 2) {
// We have 0 or 1 markers so hide the icon.
this.clusterIcon_.hide();
return;
}
this.url_ = styles[index - 1].url;
this.height_ = styles[index - 1].height;
this.width_ = styles[index - 1].width;
this.textColor_ = styles[index - 1].opt_textColor;
this.anchor_ = styles[index - 1].opt_anchor;
this.latlng_ = latlng;
this.index_ = index;
 
var numStyles = this.markerClusterer_.getStyles().length;
var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
this.clusterIcon_.setCenter(this.center_);
this.clusterIcon_.setSums(sums);
this.clusterIcon_.show();
};
 
 
/**
* A cluster icon
*
* @param {Cluster} cluster The cluster to be associated with.
* @param {Object} styles An object that has style properties:
* 'url': (string) The image url.
* 'height': (number) The image height.
* 'width': (number) The image width.
* 'anchor': (Array) The anchor position of the label text.
* 'textColor': (string) The text color.
* 'textSize': (number) The text size.
* 'backgroundPosition: (string) The background postition x, y.
* @param {number=} opt_padding Optional padding to apply to the cluster icon.
* @constructor
* @extends google.maps.OverlayView
* @ignore
*/
function ClusterIcon(cluster, styles, opt_padding) {
cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
 
this.styles_ = styles;
this.text_ = count;
this.padding_ = padding;
this.padding_ = opt_padding || 0;
this.cluster_ = cluster;
this.center_ = null;
this.map_ = cluster.getMap();
this.div_ = null;
this.sums_ = null;
this.visible_ = false;
 
this.setMap(this.map_);
}
 
ClusterMarker_.prototype = new GOverlay();
 
/**
* Initialize cluster marker.
* Triggers the clusterclick event and zoom's if the option is set.
*/
ClusterIcon.prototype.triggerClusterClick = function() {
var markerClusterer = this.cluster_.getMarkerClusterer();
 
// Trigger the clusterclick event.
google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
 
if (markerClusterer.isZoomOnClick()) {
// Center the map on this cluster.
this.map_.panTo(this.cluster_.getCenter());
 
// Zoom into the cluster.
this.map_.fitBounds(this.cluster_.getBounds());
}
};
 
 
/**
* Adding the cluster icon to the dom.
* @ignore
*/
ClusterIcon.prototype.onAdd = function() {
this.div_ = document.createElement('DIV');
if (this.visible_) {
var pos = this.getPosFromLatLng_(this.center_);
this.div_.style.cssText = this.createCss(pos);
this.div_.innerHTML = this.sums_.text;
}
 
var panes = this.getPanes();
panes.overlayImage.appendChild(this.div_);
 
var that = this;
google.maps.event.addDomListener(this.div_, 'click', function() {
that.triggerClusterClick();
});
};
 
 
/**
* Returns the position to place the div dending on the latlng.
*
* @param {google.maps.LatLng} latlng The position in latlng.
* @return {google.maps.Point} The position in pixels.
* @private
*/
ClusterMarker_.prototype.initialize = function (map) {
this.map_ = map;
var div = document.createElement("div");
var latlng = this.latlng_;
var pos = map.fromLatLngToDivPixel(latlng);
ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) {
var pos = this.getProjection().fromLatLngToDivPixel(latlng);
pos.x -= parseInt(this.width_ / 2, 10);
pos.y -= parseInt(this.height_ / 2, 10);
var mstyle = "";
if (document.all) {
mstyle = 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="' + this.url_ + '");';
} else {
mstyle = "background:url(" + this.url_ + ");";
return pos;
};
 
 
/**
* Draw the icon.
* @ignore
*/
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';
}
if (typeof this.anchor_ === "object") {
if (typeof this.anchor_[0] === "number" && this.anchor_[0] > 0 && this.anchor_[0] < this.height_) {
mstyle += 'height:' + (this.height_ - this.anchor_[0]) + 'px;padding-top:' + this.anchor_[0] + 'px;';
} else {
mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
}
if (typeof this.anchor_[1] === "number" && this.anchor_[1] > 0 && this.anchor_[1] < this.width_) {
mstyle += 'width:' + (this.width_ - this.anchor_[1]) + 'px;padding-left:' + this.anchor_[1] + 'px;';
} else {
mstyle += 'width:' + this.width_ + 'px;text-align:center;';
}
} else {
mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
mstyle += 'width:' + this.width_ + 'px;text-align:center;';
};
 
 
/**
* Hide the icon.
*/
ClusterIcon.prototype.hide = function() {
if (this.div_) {
this.div_.style.display = 'none';
}
var txtColor = this.textColor_ ? this.textColor_ : 'black';
this.visible_ = false;
};
 
div.style.cssText = mstyle + 'cursor:pointer;top:' + pos.y + "px;left:" +
pos.x + "px;color:" + txtColor + ";position:absolute;font-size:11px;" +
'font-family:Arial,sans-serif;font-weight:bold';
div.innerHTML = this.text_;
map.getPane(G_MAP_MAP_PANE).appendChild(div);
var padding = this.padding_;
GEvent.addDomListener(div, "click", function () {
var pos = map.fromLatLngToDivPixel(latlng);
var sw = new GPoint(pos.x - padding, pos.y + padding);
sw = map.fromDivPixelToLatLng(sw);
var ne = new GPoint(pos.x + padding, pos.y - padding);
ne = map.fromDivPixelToLatLng(ne);
var zoom = map.getBoundsZoomLevel(new GLatLngBounds(sw, ne), map.getSize());
map.setCenter(latlng, zoom);
});
this.div_ = div;
 
/**
* Position and show the icon.
*/
ClusterIcon.prototype.show = function() {
if (this.div_) {
var pos = this.getPosFromLatLng_(this.center_);
this.div_.style.cssText = this.createCss(pos);
this.div_.style.display = '';
}
this.visible_ = true;
};
 
 
/**
* Remove this overlay.
* @private
* Remove the icon from the map
*/
ClusterMarker_.prototype.remove = function () {
this.div_.parentNode.removeChild(this.div_);
ClusterIcon.prototype.remove = function() {
this.setMap(null);
};
 
 
/**
* Copy this overlay.
* @private
* Implementation of the onRemove interface.
* @ignore
*/
ClusterMarker_.prototype.copy = function () {
return new ClusterMarker_(this.latlng_, this.index_, this.text_, this.styles_, this.padding_);
ClusterIcon.prototype.onRemove = function() {
if (this.div_ && this.div_.parentNode) {
this.hide();
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
};
 
 
/**
* Redraw this overlay.
* @private
* Set the sums of the icon.
*
* @param {Object} sums The sums containing:
* 'text': (string) The text to display in the icon.
* 'index': (number) The style index of the icon.
*/
ClusterMarker_.prototype.redraw = function (force) {
if (!force) {
return;
ClusterIcon.prototype.setSums = function(sums) {
this.sums_ = sums;
this.text_ = sums.text;
this.index_ = sums.index;
if (this.div_) {
this.div_.innerHTML = sums.text;
}
var pos = this.map_.fromLatLngToDivPixel(this.latlng_);
pos.x -= parseInt(this.width_ / 2, 10);
pos.y -= parseInt(this.height_ / 2, 10);
this.div_.style.top = pos.y + "px";
this.div_.style.left = pos.x + "px";
 
this.useStyle();
};
 
 
/**
* Hide this cluster marker.
* Sets the icon to the the styles.
*/
ClusterMarker_.prototype.hide = function () {
this.div_.style.display = "none";
ClusterIcon.prototype.useStyle = function() {
var index = Math.max(0, this.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.textColor_ = style['textColor'];
this.anchor_ = style['anchor'];
this.textSize_ = style['textSize'];
this.backgroundPosition_ = style['backgroundPosition'];
};
 
 
/**
* Show this cluster marker.
* Sets the center of the icon.
*
* @param {google.maps.LatLng} center The latlng to set as the center.
*/
ClusterMarker_.prototype.show = function () {
this.div_.style.display = "";
ClusterIcon.prototype.setCenter = function(center) {
this.center_ = center;
};
 
 
/**
* Get whether the cluster marker is hidden.
* @return {Boolean}
* Create the css text based on the position of the icon.
*
* @param {google.maps.Point} pos The position.
* @return {string} The css style text.
*/
ClusterMarker_.prototype.isHidden = function () {
return this.div_.style.display === "none";
ClusterIcon.prototype.createCss = function(pos) {
var style = [];
if (document.all) {
style.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
'sizingMethod=scale,src="' + this.url_ + '");');
} else {
style.push('background-image:url(' + this.url_ + ');');
var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0';
style.push('background-position:' + 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;');
}
 
var txtColor = this.textColor_ ? this.textColor_ : 'black';
var txtSize = this.textSize_ ? this.textSize_ : 11;
 
style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' +
txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
return style.join('');
};
 
 
// Export Symbols for Closure
// If you are not going to compile with closure then you can remove the
// code below.
window['MarkerClusterer'] = MarkerClusterer;
MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker;
MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers;
MarkerClusterer.prototype['clearMarkers'] =
MarkerClusterer.prototype.clearMarkers;
MarkerClusterer.prototype['getCalculator'] =
MarkerClusterer.prototype.getCalculator;
MarkerClusterer.prototype['getGridSize'] =
MarkerClusterer.prototype.getGridSize;
MarkerClusterer.prototype['getExtendedBounds'] =
MarkerClusterer.prototype.getExtendedBounds;
MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap;
MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers;
MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom;
MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles;
MarkerClusterer.prototype['getTotalClusters'] =
MarkerClusterer.prototype.getTotalClusters;
MarkerClusterer.prototype['getTotalMarkers'] =
MarkerClusterer.prototype.getTotalMarkers;
MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw;
MarkerClusterer.prototype['removeMarker'] =
MarkerClusterer.prototype.removeMarker;
MarkerClusterer.prototype['removeMarkers'] =
MarkerClusterer.prototype.removeMarkers;
MarkerClusterer.prototype['resetViewport'] =
MarkerClusterer.prototype.resetViewport;
MarkerClusterer.prototype['setCalculator'] =
MarkerClusterer.prototype.setCalculator;
MarkerClusterer.prototype['setGridSize'] =
MarkerClusterer.prototype.setGridSize;
MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd;
MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw;
 
Cluster.prototype['getCenter'] = Cluster.prototype.getCenter;
Cluster.prototype['getSize'] = Cluster.prototype.getSize;
Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers;
 
ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd;
ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw;
ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove;