1/* 2 OverlappingMarkerSpiderfier 3https://github.com/jawj/OverlappingMarkerSpiderfier 4Copyright (c) 2011 - 2017 George MacKerron 5Released under the MIT licence: http://opensource.org/licenses/mit-license 6Note: The Google Maps API v3 must be included *before* this code 7*/ 8(function(){var m,t,w,y,u,z={}.hasOwnProperty,A=[].slice;this.OverlappingMarkerSpiderfier=function(){function r(a,d){var b,f,e;this.map=a;null==d&&(d={});null==this.constructor.N&&(this.constructor.N=!0,h=google.maps,l=h.event,p=h.MapTypeId,c.keepSpiderfied=!1,c.ignoreMapClick=!1,c.markersWontHide=!1,c.markersWontMove=!1,c.basicFormatEvents=!1,c.nearbyDistance=20,c.circleSpiralSwitchover=9,c.circleFootSeparation=23,c.circleStartAngle=x/12,c.spiralFootSeparation=26,c.spiralLengthStart=11,c.spiralLengthFactor= 9 4,c.spiderfiedZIndex=h.Marker.MAX_ZINDEX+2E4,c.highlightedLegZIndex=h.Marker.MAX_ZINDEX+1E4,c.usualLegZIndex=h.Marker.MAX_ZINDEX+1,c.legWeight=1.5,c.legColors={usual:{},highlighted:{}},e=c.legColors.usual,f=c.legColors.highlighted,e[p.HYBRID]=e[p.SATELLITE]="#fff",f[p.HYBRID]=f[p.SATELLITE]="#f00",e[p.TERRAIN]=e[p.ROADMAP]="#444",f[p.TERRAIN]=f[p.ROADMAP]="#f00",this.constructor.j=function(a){return this.setMap(a)},this.constructor.j.prototype=new h.OverlayView,this.constructor.j.prototype.draw=function(){}); 10 for(b in d)z.call(d,b)&&(f=d[b],this[b]=f);this.g=new this.constructor.j(this.map);this.C();this.c={};this.B=this.l=null;this.addListener("click",function(a,b){return l.trigger(a,"spider_click",b)});this.addListener("format",function(a,b){return l.trigger(a,"spider_format",b)});this.ignoreMapClick||l.addListener(this.map,"click",function(a){return function(){return a.unspiderfy()}}(this));l.addListener(this.map,"maptypeid_changed",function(a){return function(){return a.unspiderfy()}}(this));l.addListener(this.map, 11 "zoom_changed",function(a){return function(){a.unspiderfy();if(!a.basicFormatEvents)return a.h()}}(this))}var l,h,m,v,p,c,t,x,u;c=r.prototype;t=[r,c];m=0;for(v=t.length;m<v;m++)u=t[m],u.VERSION="1.0.3";x=2*Math.PI;h=l=p=null;r.markerStatus={SPIDERFIED:"SPIDERFIED",SPIDERFIABLE:"SPIDERFIABLE",UNSPIDERFIABLE:"UNSPIDERFIABLE",UNSPIDERFIED:"UNSPIDERFIED"};c.C=function(){this.a=[];this.s=[]};c.addMarker=function(a,d){a.setMap(this.map);return this.trackMarker(a,d)};c.trackMarker=function(a,d){var b;if(null!= 12 a._oms)return this;a._oms=!0;b=[l.addListener(a,"click",function(b){return function(d){return b.V(a,d)}}(this))];this.markersWontHide||b.push(l.addListener(a,"visible_changed",function(b){return function(){return b.D(a,!1)}}(this)));this.markersWontMove||b.push(l.addListener(a,"position_changed",function(b){return function(){return b.D(a,!0)}}(this)));null!=d&&b.push(l.addListener(a,"spider_click",d));this.s.push(b);this.a.push(a);this.basicFormatEvents?this.trigger("format",a,this.constructor.markerStatus.UNSPIDERFIED): 13 (this.trigger("format",a,this.constructor.markerStatus.UNSPIDERFIABLE),this.h());return this};c.D=function(a,d){if(!this.J&&!this.K)return null==a._omsData||!d&&a.getVisible()||this.unspiderfy(d?a:null),this.h()};c.getMarkers=function(){return this.a.slice(0)};c.removeMarker=function(a){this.forgetMarker(a);return a.setMap(null)};c.forgetMarker=function(a){var d,b,f,e,g;null!=a._omsData&&this.unspiderfy();d=this.A(this.a,a);if(0>d)return this;g=this.s.splice(d,1)[0];b=0;for(f=g.length;b<f;b++)e=g[b], 14 l.removeListener(e);delete a._oms;this.a.splice(d,1);this.h();return this};c.removeAllMarkers=c.clearMarkers=function(){var a,d,b,f;f=this.getMarkers();this.forgetAllMarkers();a=0;for(d=f.length;a<d;a++)b=f[a],b.setMap(null);return this};c.forgetAllMarkers=function(){var a,d,b,f,e,g,c,q;this.unspiderfy();q=this.a;a=d=0;for(b=q.length;d<b;a=++d){g=q[a];e=this.s[a];c=0;for(a=e.length;c<a;c++)f=e[c],l.removeListener(f);delete g._oms}this.C();return this};c.addListener=function(a,d){var b;(null!=(b=this.c)[a]? 15 b[a]:b[a]=[]).push(d);return this};c.removeListener=function(a,d){var b;b=this.A(this.c[a],d);0>b||this.c[a].splice(b,1);return this};c.clearListeners=function(a){this.c[a]=[];return this};c.trigger=function(){var a,d,b,f,e,g;d=arguments[0];
15a=2<=arguments.length?A.call(arguments,1):[];d=null!=(b=this.c[d])?b:[];g=[];f=0;for(e=d.length;f<e;f++)b=d[f],g.push(b.apply(null,a));return g};c.L=function(a,d){var b,f,e,g,c;g=this.circleFootSeparation*(2+a)/x;f=x/a;c=[];for(b=e=0;0<=a?e<a:e>a;b=0<=a?++e:--e)b= 16 this.circleStartAngle+b*f,c.push(new h.Point(d.x+g*Math.cos(b),d.y+g*Math.sin(b)));return c};c.M=function(a,d){var b,f,e,c,k;c=this.spiralLengthStart;b=0;k=[];for(f=e=0;0<=a?e<a:e>a;f=0<=a?++e:--e)b+=this.spiralFootSeparation/c+5E-4*f,f=new h.Point(d.x+c*Math.cos(b),d.y+c*Math.sin(b)),c+=x*this.spiralLengthFactor/b,k.push(f);return k};c.V=function(a,d){var b,f,e,c,k,q,n,l,h;(q=null!=a._omsData)&&this.keepSpiderfied||this.unspiderfy();if(q||this.map.getStreetView().getVisible()||"GoogleEarthAPI"=== 17 this.map.getMapTypeId())return this.trigger("click",a,d);q=[];n=[];b=this.nearbyDistance;l=b*b;k=this.f(a.position);h=this.a;b=0;for(f=h.length;b<f;b++)e=h[b],null!=e.map&&e.getVisible()&&(c=this.f(e.position),this.i(c,k)<l?q.push({R:e,G:c}):n.push(e));return 1===q.length?this.trigger("click",a,d):this.W(q,n)};c.markersNearMarker=function(a,d){var b,f,e,c,k,q,n,l,h,m;null==d&&(d=!1);if(null==this.g.getProjection())throw"Must wait for 'idle' event on map before calling markersNearMarker";b=this.nearbyDistance; 18 n=b*b;k=this.f(a.position);q=[];l=this.a;b=0;for(f=l.length;b<f&&!(e=l[b],e!==a&&null!=e.map&&e.getVisible()&&(c=this.f(null!=(h=null!=(m=e._omsData)?m.v:void 0)?h:e.position),this.i(c,k)<n&&(q.push(e),d)));b++);return q};c.F=function(){var a,d,b,f,e,c,k,l,n,h,m;if(null==this.g.getProjection())throw"Must wait for 'idle' event on map before calling markersNearAnyOtherMarker";n=this.nearbyDistance;n*=n;var p;e=this.a;p=[];h=0;for(d=e.length;h<d;h++)f=e[h],p.push({H:this.f(null!=(a=null!=(b=f._omsData)? 19 b.v:void 0)?a:f.position),b:!1});h=this.a;a=b=0;for(f=h.length;b<f;a=++b)if(d=h[a],null!=d.getMap()&&d.getVisible()&&(c=p[a],!c.b))for(m=this.a,d=l=0,e=m.length;l<e;d=++l)if(k=m[d],d!==a&&null!=k.getMap()&&k.getVisible()&&(k=p[d],(!(d<a)||k.b)&&this.i(c.H,k.H)<n)){c.b=k.b=!0;break}return p};c.markersNearAnyOtherMarker=function(){var a,d,b,c,e,g,k;e=this.F();g=this.a;k=[];a=d=0;for(b=g.length;d<b;a=++d)c=g[a],e[a].b&&k.push(c);return k};c.setImmediate=function(a){return window.setTimeout(a,0)};c.h= 20 function(){if(!this.basicFormatEvents&&null==this.l)return this.l=this.setImmediate(function(a){return function(){a.l=null;return null!=a.g.getProjection()?a.w():null!=a.B?void 0:a.B=l.addListenerOnce(a.map,"idle",function(){return a.w()})}}(this))};c.w=function(){var a,d,b,c,e,g,k;if(this.basicFormatEvents){e=[];d=0;for(b=markers.length;d<b;d++)c=markers[d],a=null!=c._omsData?"SPIDERFIED":"UNSPIDERFIED",e.push(this.trigger("format",c,this.constructor.markerStatus[a]));return e}e=this.F();g=this.a; 21 k=[];a=b=0;for(d=g.length;b<d;a=++b)c=g[a],a=null!=c._omsData?"SPIDERFIED":e[a].b?"SPIDERFIABLE":"UNSPIDERFIABLE",k.push(this.trigger("format",c,this.constructor.markerStatus[a]));return k};c.P=function(a){return{m:function(d){return function(){return a._omsData.o.setOptions({strokeColor:d.legColors.highlighted[d.map.mapTypeId],zIndex:d.highlightedLegZIndex})}}(this),u:function(d){return function(){return a._omsData.o.setOptions({strokeColor:d.legColors.usual[d.map.mapTypeId],zIndex:d.usualLegZIndex})}}(this)}}; 22 c.W=function(a,d){var b,c,e,g,k,q,n,m,p,r;this.J=!0;r=a.length;b=this.T(function(){var b,d,c;c=[];b=0;for(d=a.length;b<d;b++)m=a[b],c.push(m.G);return c}());g=r>=this.circleSpiralSwitchover?this.M(r,b).reverse():this.L(r,b);b=function(){var b,d,f;f=[];
22b=0;for(d=g.length;b<d;b++)e=g[b],c=this.U(e),p=this.S(a,function(a){return function(b){return a.i(b.G,e)}}(this)),n=p.R,q=new h.Polyline({map:this.map,path:[n.position,c],strokeColor:this.legColors.usual[this.map.mapTypeId],strokeWeight:this.legWeight, 23 zIndex:this.usualLegZIndex}),n._omsData={v:n.getPosition(),X:n.getZIndex(),o:q},this.legColors.highlighted[this.map.mapTypeId]!==this.legColors.usual[this.map.mapTypeId]&&(k=this.P(n),n._omsData.O={m:l.addListener(n,"mouseover",k.m),u:l.addListener(n,"mouseout",k.u)}),this.trigger("format",n,this.constructor.markerStatus.SPIDERFIED),n.setPosition(c),n.setZIndex(Math.round(this.spiderfiedZIndex+e.y)),f.push(n);return f}.call(this);delete this.J;this.I=!0;return this.trigger("spiderfy",b,d)};c.unspiderfy= 24 function(a){var d,b,c,e,g,k,h;null==a&&(a=null);if(null==this.I)return this;this.K=!0;h=[];g=[];k=this.a;d=0;for(b=k.length;d<b;d++)e=k[d],null!=e._omsData?(e._omsData.o.setMap(null),e!==a&&e.setPosition(e._omsData.v),e.setZIndex(e._omsData.X),c=e._omsData.O,null!=c&&(l.removeListener(c.m),l.removeListener(c.u)),delete e._omsData,e!==a&&(c=this.basicFormatEvents?"UNSPIDERFIED":"SPIDERFIABLE",this.trigger("format",e,this.constructor.markerStatus[c])),h.push(e)):g.push(e);delete this.K;delete this.I; 25 this.trigger("unspiderfy",h,g);return this};c.i=function(a,d){var b,c;b=a.x-d.x;c=a.y-d.y;return b*b+c*c};c.T=function(a){var c,b,f,e,g;c=e=g=0;for(b=a.length;c<b;c++)f=a[c],e+=f.x,g+=f.y;a=a.length;return new h.Point(e/a,g/a)};c.f=function(a){return this.g.getProjection().fromLatLngToDivPixel(a)};c.U=function(a){return this.g.getProjection().fromDivPixelToLatLng(a)};c.S=function(a,c){var b,d,e,g,k,h;e=k=0;for(h=a.length;k<h;e=++k)if(g=a[e],g=c(g),"undefined"===typeof b||null===b||g<d)d=g,b=e;return a.splice(b, 26 1)[0]};c.A=function(a,c){var b,d,e,g;if(null!=a.indexOf)return a.indexOf(c);b=d=0;for(e=a.length;d<e;b=++d)if(g=a[b],g===c)return b;return-1};return r}();t=/(\?.*(&|&)|\?)spiderfier_callback=(\w+)/;m=document.currentScript;null==m&&(m=function(){var m,l,h,w,v;h=document.getElementsByTagName("script");v=[];m=0;for(l=h.length;m<l;m++)u=h[m],null!=(w=u.getAttribute("src"))&&w.match(t)&&v.push(u);return v}()[0]);if(null!=m&&(m=null!=(w=m.getAttribute("src"))?null!=(y=w.match(t))?y[3]:void 0:void 0)&& 27 "function"===typeof window[m])window[m]();"function"===typeof window.spiderfier_callback&&window.spiderfier_callback()}).call(this); 28/* Thu 11 May 2017 08:40:57 BST */ 29 30 31/** 32 * @name MarkerClusterer for Google Maps v3 33 * @version version 1.0.3 34 * @author Luke Mahe 35 * @fileoverview 36 * The library creates and manages per-zoom-level clusters for large amounts of 37 * markers. 38 */ 39 40/** 41 * @license 42 * Licensed under the Apache License, Version 2.0 (the "License"); 43 * you may not use this file except in compliance with the License. 44 * You may obtain a copy of the License at 45 * 46 * http://www.apache.org/licenses/LICENSE-2.0 47 * 48 * Unless required by applicable law or agreed to in writing, software 49 * distributed under the License is distributed on an "AS IS" BASIS, 50 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 51 * See the License for the specific language governing permissions and 52 * limitations under the License. 53 */ 54 55 56/** 57 * A Marker Clusterer that clusters markers. 58 * 59 * @param {google.maps.Map} map The Google map to attach to. 60 * @param {Array.<google.maps.Marker>=} opt_markers Optional markers to add to 61 * the cluster. 62 * @param {Object=} opt_options support the following options: 63 * 'gridSize': (number) The grid size of a cluster in pixels. 64 * 'maxZoom': (number) The maximum zoom level that a marker can be part of a 65 * cluster. 66 * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a 67 * cluster is to zoom into it. 68 * 'imagePath': (string) The base URL where the images representing 69 * clusters will be found. The full URL will be: 70 * {imagePath}[1-5].{imageExtension} 71 * Default: '../images/m'. 72 * 'imageExtension': (string) The suffix for images URL representing
73 * clusters will be found. See _imagePath_ for details. 74 * 75 * Default: 'png'. 76 * 'averageCenter': (boolean) Whether the center of each cluster should be 77 * the average of all markers in the cluster. 78 * 'minimumClusterSize': (number) The minimum number of markers to be in a 79 * cluster before the markers are hidden and a count 80 * is shown. 81 * 'styles': (object) An object that has style properties: 82 * 'url': (string) The image url. 83 * 'height': (number) The image height. 84 * 'width': (number) The image width. 85 * 'anchor': (Array) The anchor position of the label text. 86 * 'textColor': (string) The text color. 87 * 'textSize': (number) The text size. 88 * 'backgroundPosition': (string) The position of the backgound x, y. 89 * @constructor 90 * @extends google.maps.OverlayView 91 */ 92function MarkerClusterer(map, opt_markers, opt_options) { 93 // MarkerClusterer implements google.maps.OverlayView interface. We use the 94 // extend function to extend MarkerClusterer with google.maps.OverlayView 95 // because it might not always be available when the code is defined so we 96 // look for it at the last possible moment. If it doesn't exist now then 97 // there is no point going ahead :) 98 this.extend(MarkerClusterer, google.maps.OverlayView); 99 this.map_ = map; 100 101 /** 102 * @type {Array.<google.maps.Marker>} 103 * @private 104 */ 105 this.markers_ = []; 106 107 /** 108 * @type {Array.<Cluster>} 109 */ 110 this.clusters_ = []; 111 112 this.sizes = [53, 56, 66, 78, 90]; 113 114 /** 115 * @private 116 */ 117 this.styles_ = []; 118 119 /** 120 * @type {boolean} 121 * @private 122 */ 123 this.ready_ = false; 124 125 var options = opt_options || {}; 126 127 /** 128 * @type {number} 129 * @private 130 */ 131 this.gridSize_ = options['gridSize'] || 60; 132 133 /** 134 * @private 135 */ 136 this.minClusterSize_ = options['minimumClusterSize'] || 2; 137 138 139 /** 140 * @type {?number} 141 * @private 142 */ 143 this.maxZoom_ = options['maxZoom'] || null; 144 145 this.styles_ = options['styles'] || []; 146 147 /** 148 * @type {string} 149 * @private 150 */ 151 this.imagePath_ = options['imagePath'] || 152 this.MARKER_CLUSTER_IMAGE_PATH_; 153 154 /** 155 * @type {string} 156 * @private 157 */ 158 this.imageExtension_ = options['imageExtension'] || 159 this.MARKER_CLUSTER_IMAGE_EXTENSION_; 160 161 /** 162 * @type {boolean} 163 * @private 164 */ 165 this.zoomOnClick_ = true; 166 167 if (options['zoomOnClick'] != undefined) { 168 this.zoomOnClick_ = options['zoomOnClick']; 169 } 170 171 /** 172 * @type {boolean} 173 * @private 174 */ 175 this.averageCenter_ = false; 176 177 if (options['averageCenter'] != undefined) { 178 this.averageCenter_ = options['averageCenter']; 179 } 180 181 this.setupStyles_(); 182 183 this.setMap(map); 184 185 /** 186 * @type {number} 187 * @private 188 */ 189 this.prevZoom_ = this.map_.getZoom(); 190 191 // Add the map event listeners 192 var that = this; 193 google.maps.event.addListener(this.map_, 'zoom_changed', function () { 194 // Determines map type and prevent illegal zoom levels 195 var zoom = that.map_.getZoom(); 196 var minZoom = that.map_.minZoom || 0; 197 var maxZoom = Math.min(that.map_.maxZoom || 100, 198 that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom); 199 zoom = Math.min(Math.max(zoom, minZoom), maxZoom); 200 201 if (that.prevZoom_ != zoom) { 202 that.prevZoom_ = zoom; 203 that.resetViewport(); 204 } 205 }); 206 207 google.maps.event.addListener(this.map_, 'idle', function () { 208 that.redraw(); 209 }); 210 211 // Finally, add the markers 212 if (opt_markers && (opt_markers.length || Object.keys(opt_markers).length)) { 213 this.addMarkers(opt_markers, false); 214 } 215} 216 217 218/** 219 * The marker cluster image path. 220 * 221 * @type {string} 222 * @private 223 */ 224MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = '../images/m'; 225 226 227/** 228 * The marker cluster image path. 229 * 230 * @type {string} 231 * @private 232 */ 233MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; 234 235 236/** 237 * Extends a objects prototype by anothers. 238 * 239 * @param {Object} obj1 The object to be extended. 240 * @param {Object} obj2 The object to extend with. 241 * @return {Object} The new extended object. 242 * @ignore 243 */ 244MarkerClusterer.prototype.extend = function (obj1, obj2) { 245 return (function (object) { 246 for (var property in object.prototype) { 247 this.prototype[property] = object.prototype[property]; 248 } 249 return this; 250 }).apply(obj1, [obj2]); 251}; 252 253 254/** 255 * Implementaion of the interface method. 256 * @ignore 257 */ 258MarkerClusterer.prototype.onAdd = function () { 259 this.setReady_(true); 260}; 261 262/** 263 * Implementaion of the interface method. 264 * @ignore 265 */ 266MarkerClusterer.prototype.draw = function () { 267}; 268 269/** 270 * Sets up the styles object. 271 * 272 * @private 273 */ 274MarkerClusterer.prototype.setupStyles_ = function () { 275 if (this.styles_.length) { 276 return; 277 } 278 279 for (var i = 0, size; size = this.sizes[i]; i++) { 280 this.styles_.push({ 281 url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_, 282 height: size, 283 width: size 284 }); 285 } 286}; 287 288/** 289 * Fit the map to the bounds of the markers in the clusterer. 290 */ 291MarkerClusterer.prototype.fitMapToMarkers = function () { 292 var markers = this.getMarkers(); 293 var bounds = new google.maps.LatLngBounds(); 294 for (var i = 0, marker; marker = markers[i]; i++) { 295 bounds.extend(marker.getPosition()); 296 } 297 298 this.map_.fitBounds(bounds); 299}; 300 301 302/** 303 * Sets the styles. 304 * 305 * @param {Object} styles The style to set. 306 */ 307MarkerClusterer.prototype.setStyles = function (styles) { 308 this.styles_ = styles; 309}; 310 311 312/** 313 * Gets the styles. 314 * 315 * @return {Object} The styles object. 316 */ 317MarkerClusterer.prototype.getStyles = function () { 318 return this.styles_; 319}; 320 321 322/** 323 * Whether zoom on click is set. 324 * 325 * @return {boolean} True if zoomOnClick_ is set. 326 */ 327MarkerClusterer.prototype.isZoomOnClick = function () { 328 return this.zoomOnClick_; 329}; 330 331/** 332 * Whether average center is set. 333 * 334 * @return {boolean} True if averageCenter_ is set. 335 */ 336MarkerClusterer.prototype.isAverageCenter = function () { 337 return this.averageCenter_; 338}; 339 340 341/** 342 * Returns the array of markers in the clusterer. 343 * 344 * @return {Array.<google.maps.Marker>} The markers. 345 */ 346MarkerClusterer.prototype.getMarkers = function () { 347 return this.markers_; 348}; 349 350 351/** 352 * Returns the number of markers in the clusterer 353 * 354 * @return {Number} The number of markers. 355 */ 356MarkerClusterer.prototype.getTotalMarkers = function () { 357 return this.markers_.length; 358}; 359 360 361/** 362 * Sets the max zoom for the clusterer. 363 * 364 * @param {number} maxZoom The max zoom level. 365 */ 366MarkerClusterer.prototype.setMaxZoom = function (maxZoom) { 367 this.maxZoom_ = maxZoom; 368}; 369 370 371/** 372 * Gets the max zoom for the clusterer. 373 * 374 * @return {number} The max zoom level. 375 */ 376MarkerClusterer.prototype.getMaxZoom = function () { 377 return this.maxZoom_; 378}; 379 380 381/** 382 * The function for calculating the cluster icon image. 383 * 384 * @param {Array.<google.maps.Marker>} markers The markers in the clusterer. 385 * @param {number} numStyles The number of styles available. 386 * @return {Object} A object properties: 'text' (string) and 'index' (number). 387 * @private 388 */ 389MarkerClusterer.prototype.calculator_ = function (markers, numStyles) { 390 var index = 0; 391 var count = markers.length; 392 var dv = count; 393 while (dv !== 0) { 394 dv = parseInt(dv / 10, 10); 395 index++; 396 } 397 398 index = Math.min(index, numStyles); 399 return { 400 text: count, 401 index: index 402 }; 403}; 404 405 406/** 407 * Set the calculator function. 408 * 409 * @param {function(Array, number)} calculator The function to set as the 410 * calculator. The function should return a object properties: 411 * 'text' (string) and 'index' (number). 412 * 413 */ 414MarkerClusterer.prototype.setCalculator = function (calculator) { 415 this.calculator_ = calculator; 416}; 417 418 419/** 420 * Get the calculator function. 421 * 422 * @return {function(Array, number)} the calculator function. 423 */ 424MarkerClusterer.prototype.getCalculator = function () { 425 return this.calculator_; 426}; 427 428 429/** 430 * Add an array of markers to the clusterer. 431 * 432 * @param {Array.<google.maps.Marker>} markers The markers to add. 433 * @param {boolean=}
433 opt_nodraw Whether to redraw the clusters. 434 */ 435MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) { 436 if (markers.length) { 437 for (var i = 0, marker; marker = markers[i]; i++) { 438 this.pushMarkerTo_(marker); 439 } 440 } else if (Object.keys(markers).length) { 441 for (var marker in markers) { 442 this.pushMarkerTo_(markers[marker]); 443 } 444 } 445 if (!opt_nodraw) { 446 this.redraw(); 447 } 448}; 449 450 451/** 452 * Pushes a marker to the clusterer. 453 * 454 * @param {google.maps.Marker} marker The marker to add. 455 * @private 456 */ 457MarkerClusterer.prototype.pushMarkerTo_ = function (marker) { 458 marker.isAdded = false; 459 if (marker['draggable']) { 460 // If the marker is draggable add a listener so we update the clusters on 461 // the drag end. 462 var that = this; 463 google.maps.event.addListener(marker, 'dragend', function () { 464 marker.isAdded = false; 465 that.repaint(); 466 }); 467 } 468 this.markers_.push(marker); 469}; 470 471 472/** 473 * Adds a marker to the clusterer and redraws if needed. 474 * 475 * @param {google.maps.Marker} marker The marker to add. 476 * @param {boolean=} opt_nodraw Whether to redraw the clusters. 477 */ 478MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) { 479 this.pushMarkerTo_(marker); 480 if (!opt_nodraw) { 481 this.redraw(); 482 } 483}; 484 485 486/** 487 * Removes a marker and returns true if removed, false if not 488 * 489 * @param {google.maps.Marker} marker The marker to remove 490 * @return {boolean} Whether the marker was removed or not 491 * @private 492 */ 493MarkerClusterer.prototype.removeMarker_ = function (marker) { 494 var index = -1; 495 if (this.markers_.indexOf) { 496 index = this.markers_.indexOf(marker); 497 } else { 498 for (var i = 0, m; m = this.markers_[i]; i++) { 499 if (m == marker) { 500 index = i; 501 break; 502 } 503 } 504 } 505 506 if (index == -1) { 507 // Marker is not in our list of markers. 508 return false; 509 } 510 511 marker.setMap(null); 512 513 this.markers_.splice(index, 1); 514 515 return true; 516}; 517 518 519/** 520 * Remove a marker from the cluster. 521 * 522 * @param {google.maps.Marker} marker The marker to remove. 523 * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 524 * @return {boolean} True if the marker was removed. 525 */ 526MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) { 527 var removed = this.removeMarker_(marker); 528 529 if (!opt_nodraw && removed) { 530 this.resetViewport(); 531 this.redraw(); 532 return true; 533 } else { 534 return false; 535 } 536}; 537 538 539/** 540 * Removes an array of markers from the cluster. 541 * 542 * @param {Array.<google.maps.Marker>} markers The markers to remove. 543 * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 544 */ 545MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) { 546 // create a local copy of markers if required 547 // (removeMarker_ modifies the getMarkers() array in place) 548 var markersCopy = markers === this.getMarkers() ? markers.slice() : markers; 549 var removed = false; 550 551 for (var i = 0, marker; marker = markersCopy[i]; i++) { 552 var r = this.removeMarker_(marker); 553 removed = removed || r; 554 } 555 556 if (!opt_nodraw && removed) { 557 this.resetViewport(); 558 this.redraw(); 559 return true; 560 } 561}; 562 563 564/** 565 * Sets the clusterer's ready state. 566 * 567 * @param {boolean} ready The state. 568 * @private 569 */ 570MarkerClusterer.prototype.setReady_ = function (ready) { 571 if (!this.ready_) { 572 this.ready_ = ready; 573 this.createClusters_(); 574 } 575}; 576 577 578/** 579 * Returns the number of clusters in the clusterer. 580 * 581 * @return {number} The number of clusters. 582 */ 583MarkerClusterer.prototype.getTotalClusters = function () { 584 return this.clusters_.length; 585}; 586 587 588/** 589 * Returns the google map that the clusterer is associated with. 590 * 591 * @return {google.maps.Map} The map. 592 */ 593MarkerClusterer.prototype.getMap = function () { 594 return this.map_; 595}; 596 597 598/** 599 * Sets the google map that the clusterer is associated with. 600 * 601 * @param {google.maps.Map} map The map. 602 */ 603MarkerClusterer.prototype.setMap = function (map) { 604 this.map_ = map; 605}; 606 607 608/** 609 * Returns the size of the grid. 610 * 611 * @return {number} The grid size. 612 */ 613MarkerClusterer.prototype.getGridSize = function () { 614 return this.gridSize_; 615}; 616 617 618/** 619 * Sets the size of the grid. 620 * 621 * @param {number} size The grid size. 622 */ 623MarkerClusterer.prototype.setGridSize = function (size) { 624 this.gridSize_ = size; 625}; 626 627 628/** 629 * Returns the min cluster size. 630 * 631 * @return {number} The grid size. 632 */ 633MarkerClusterer.prototype.getMinClusterSize = function () { 634 return this.minClusterSize_; 635}; 636 637/** 638 * Sets the min cluster size. 639 * 640 * @param {number} size The grid size. 641 */ 642MarkerClusterer.prototype.setMinClusterSize = function (size) { 643 this.minClusterSize_ = size; 644}; 645 646 647/** 648 * Extends a bounds object by the grid size. 649 * 650 * @param {google.maps.LatLngBounds} bounds The bounds to extend. 651 * @return {google.maps.LatLngBounds}
651 The extended bounds. 652 */ 653MarkerClusterer.prototype.getExtendedBounds = function (bounds) { 654 var projection = this.getProjection(); 655 656 // Turn the bounds into latlng. 657 var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), 658 bounds.getNorthEast().lng()); 659 var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), 660 bounds.getSouthWest().lng()); 661 662 // Convert the points to pixels and the extend out by the grid size. 663 var trPix = projection.fromLatLngToDivPixel(tr); 664 trPix.x += this.gridSize_; 665 trPix.y -= this.gridSize_; 666 667 var blPix = projection.fromLatLngToDivPixel(bl); 668 blPix.x -= this.gridSize_; 669 blPix.y += this.gridSize_; 670 671 // Convert the pixel points back to LatLng 672 var ne = projection.fromDivPixelToLatLng(trPix); 673 var sw = projection.fromDivPixelToLatLng(blPix); 674 675 // Extend the bounds to contain the new bounds. 676 bounds.extend(ne); 677 bounds.extend(sw); 678 679 return bounds; 680}; 681 682 683/** 684 * Determins if a marker is contained in a bounds. 685 * 686 * @param {google.maps.Marker} marker The marker to check. 687 * @param {google.maps.LatLngBounds} bounds The bounds to check against. 688 * @return {boolean} True if the marker is in the bounds. 689 * @private 690 */ 691MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) { 692 return bounds.contains(marker.getPosition()); 693}; 694 695 696/** 697 * Clears all clusters and markers from the clusterer. 698 */ 699MarkerClusterer.prototype.clearMarkers = function () { 700 this.resetViewport(true); 701 702 // Set the markers a empty array. 703 this.markers_ = []; 704}; 705 706 707/** 708 * Clears all existing clusters and recreates them. 709 * @param {boolean} opt_hide To also hide the marker. 710 */ 711MarkerClusterer.prototype.resetViewport = function (opt_hide) { 712 // Remove all the clusters 713 for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 714 cluster.remove(); 715 } 716 717 // Reset the markers to not be added and to be invisible. 718 for (var i = 0, marker; marker = this.markers_[i]; i++) { 719 marker.isAdded = false; 720 if (opt_hide) { 721 marker.setMap(null); 722 } 723 } 724 725 this.clusters_ = []; 726}; 727 728/** 729 * 730 */ 731MarkerClusterer.prototype.repaint = function () { 732 var oldClusters = this.clusters_.slice(); 733 this.clusters_.length = 0; 734 this.resetViewport(); 735 this.redraw(); 736 737 // Remove the old clusters. 738 // Do it in a timeout so the other clusters have been drawn first. 739 window.setTimeout(function () { 740 for (var i = 0, cluster; cluster = oldClusters[i]; i++) { 741 cluster.remove(); 742 } 743 }, 0); 744}; 745 746 747/** 748 * Redraws the clusters. 749 */ 750MarkerClusterer.prototype.redraw = function () { 751 this.createClusters_(); 752}; 753 754 755/** 756 * Calculates the distance between two latlng locations in km. 757 * @see http://www.movable-type.co.uk/scripts/latlong.html 758 * 759 * @param {google.maps.LatLng} p1 The first lat lng point. 760 * @param {google.maps.LatLng} p2 The second lat lng point. 761 * @return {number} The distance between the two points in km. 762 * @private 763 */ 764MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) { 765 if (!p1 || !p2) { 766 return 0; 767 } 768 769 var R = 6371; // Radius of the Earth in km 770 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; 771 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; 772 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 773 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * 774 Math.sin(dLon / 2) * Math.sin(dLon / 2); 775 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 776 var d = R * c; 777 return d; 778}; 779 780 781/** 782 * Add a marker to a cluster, or creates a new cluster. 783 * 784 * @param {google.maps.Marker} marker The marker to add. 785 * @private 786 */ 787MarkerClusterer.prototype.addToClosestCluster_ = function (marker) { 788 var distance = 40000; // Some large number 789 var clusterToAddTo = null; 790 var pos = marker.getPosition(); 791 for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 792 var center = cluster.getCenter(); 793 if (center) { 794 var d = this.distanceBetweenPoints_(center, marker.getPosition()); 795 if (d < distance) { 796 distance = d; 797 clusterToAddTo = cluster; 798 } 799 } 800 } 801 802 if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { 803 clusterToAddTo.addMarker(marker); 804 } else { 805 var cluster = new Cluster(this); 806 cluster.addMarker(marker);
807 this.clusters_.push(cluster); 808 } 809}; 810 811 812/** 813 * Creates the clusters. 814 * 815 * @private 816 */ 817MarkerClusterer.prototype.createClusters_ = function () { 818 if (!this.ready_) { 819 return; 820 } 821 822 // Get our current map view bounds. 823 // Create a new bounds object so we don't affect the map. 824 var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), 825 this.map_.getBounds().getNorthEast()); 826 var bounds = this.getExtendedBounds(mapBounds); 827 828 for (var i = 0, marker; marker = this.markers_[i]; i++) { 829 if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { 830 this.addToClosestCluster_(marker); 831 } 832 } 833}; 834 835 836/** 837 * A cluster that contains markers. 838 * 839 * @param {MarkerClusterer} markerClusterer The markerclusterer that this 840 * cluster is associated with. 841 * @constructor 842 * @ignore 843 */ 844function Cluster(markerClusterer) { 845 this.markerClusterer_ = markerClusterer; 846 this.map_ = markerClusterer.getMap(); 847 this.gridSize_ = markerClusterer.getGridSize(); 848 this.minClusterSize_ = markerClusterer.getMinClusterSize(); 849 this.averageCenter_ = markerClusterer.isAverageCenter(); 850 this.center_ = null; 851 this.markers_ = []; 852 this.bounds_ = null; 853 this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(), 854 markerClusterer.getGridSize()); 855} 856 857/** 858 * Determins if a marker is already added to the cluster. 859 * 860 * @param {google.maps.Marker} marker The marker to check. 861 * @return {boolean} True if the marker is already added. 862 */ 863Cluster.prototype.isMarkerAlreadyAdded = function (marker) { 864 if (this.markers_.indexOf) { 865 return this.markers_.indexOf(marker) != -1; 866 } else { 867 for (var i = 0, m; m = this.markers_[i]; i++) { 868 if (m == marker) { 869 return true; 870 } 871 } 872 } 873 return false; 874}; 875 876 877/** 878 * Add a marker the cluster. 879 * 880 * @param {google.maps.Marker} marker The marker to add. 881 * @return {boolean} True if the marker was added. 882 */ 883Cluster.prototype.addMarker = function (marker) { 884 if (this.isMarkerAlreadyAdded(marker)) { 885 return false; 886 } 887 888 if (!this.center_) { 889 this.center_ = marker.getPosition(); 890 this.calculateBounds_(); 891 } else { 892 if (this.averageCenter_) { 893 var l = this.markers_.length + 1; 894 var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l; 895 var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l; 896 this.center_ = new google.maps.LatLng(lat, lng); 897 this.calculateBounds_(); 898 } 899 } 900 901 marker.isAdded = true; 902 this.markers_.push(marker); 903 904 var len = this.markers_.length; 905 if (len < this.minClusterSize_ && marker.getMap() != this.map_) { 906 // Min cluster size not reached so show the marker. 907 marker.setMap(this.map_); 908 } 909 910 if (len == this.minClusterSize_) { 911 // Hide the markers that were showing. 912 for (var i = 0; i < len; i++) { 913 this.markers_[i].setMap(null); 914 } 915 } 916 917 if (len >= this.minClusterSize_) { 918 marker.setMap(null); 919 } 920 921 this.updateIcon(); 922 return true; 923}; 924 925 926/** 927 * Returns the marker clusterer that the cluster is associated with. 928 * 929 * @return {MarkerClusterer} The associated marker clusterer. 930 */ 931Cluster.prototype.getMarkerClusterer = function () { 932 return this.markerClusterer_; 933}; 934 935 936/** 937 * Returns the bounds of the cluster. 938 * 939 * @return {google.maps.LatLngBounds} the cluster bounds. 940 */ 941Cluster.prototype.getBounds = function () { 942 var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 943 var markers = this.getMarkers(); 944 for (var i = 0, marker; marker = markers[i]; i++) { 945 bounds.extend(marker.getPosition()); 946 } 947 return bounds; 948}; 949 950 951/** 952 * Removes the cluster 953 */ 954Cluster.prototype.remove = function () { 955 this.clusterIcon_.remove(); 956 this.markers_.length = 0; 957 delete this.markers_; 958}; 959 960 961/** 962 * Returns the number of markers in the cluster. 963 * 964 * @return {number} The number of markers in the cluster. 965 */ 966Cluster.prototype.getSize = function () { 967 return this.markers_.length; 968}; 969 970 971/** 972 * Returns a list of the markers in the cluster. 973 * 974 * @return {Array.<google.maps.Marker>} The markers in the cluster. 975 */ 976Cluster.prototype.getMarkers = function () { 977 return this.markers_; 978}; 979 980 981/** 982 * Returns the center of the cluster. 983 * 984 * @return {google.maps.LatLng} The cluster center. 985 */ 986Cluster.prototype.getCenter = function () { 987 return this.center_; 988}; 989 990 991/**
992 * Calculated the extended bounds of the cluster with the grid. 993 * 994 * @private 995 */ 996Cluster.prototype.calculateBounds_ = function () { 997 var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 998 this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); 999}; 1000 1001 1002/** 1003 * Determines if a marker lies in the clusters bounds. 1004 * 1005 * @param {google.maps.Marker} marker The marker to check. 1006 * @return {boolean} True if the marker lies in the bounds. 1007 */ 1008Cluster.prototype.isMarkerInClusterBounds = function (marker) { 1009 return this.bounds_.contains(marker.getPosition()); 1010}; 1011 1012 1013/** 1014 * Returns the map that the cluster is associated with. 1015 * 1016 * @return {google.maps.Map} The map. 1017 */ 1018Cluster.prototype.getMap = function () { 1019 return this.map_; 1020}; 1021 1022 1023/** 1024 * Updates the cluster icon 1025 */ 1026Cluster.prototype.updateIcon = function () { 1027 var zoom = this.map_.getZoom(); 1028 var mz = this.markerClusterer_.getMaxZoom(); 1029 1030 if (mz && zoom > mz) { 1031 // The zoom is greater than our max zoom so show all the markers in cluster. 1032 for (var i = 0, marker; marker = this.markers_[i]; i++) { 1033 marker.setMap(this.map_); 1034 } 1035 return; 1036 } 1037 1038 if (this.markers_.length < this.minClusterSize_) { 1039 // Min cluster size not yet reached. 1040 this.clusterIcon_.hide(); 1041 return; 1042 } 1043 1044 var numStyles = this.markerClusterer_.getStyles().length; 1045 var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); 1046 this.clusterIcon_.setCenter(this.center_); 1047 this.clusterIcon_.setSums(sums); 1048 this.clusterIcon_.show(); 1049}; 1050 1051 1052/** 1053 * A cluster icon 1054 * 1055 * @param {Cluster} cluster The cluster to be associated with. 1056 * @param {Object} styles An object that has style properties: 1057 * 'url': (string) The image url. 1058 * 'height': (number) The image height. 1059 * 'width': (number) The image width. 1060 * 'anchor': (Array) The anchor position of the label text. 1061 * 'textColor': (string) The text color. 1062 * 'textSize': (number) The text size. 1063 * 'backgroundPosition: (string) The background postition x, y. 1064 * @param {number=} opt_padding Optional padding to apply to the cluster icon. 1065 * @constructor 1066 * @extends google.maps.OverlayView 1067 * @ignore 1068 */ 1069function ClusterIcon(cluster, styles, opt_padding) { 1070 cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); 1071 1072 this.styles_ = styles; 1073 this.padding_ = opt_padding || 0; 1074 this.cluster_ = cluster; 1075 this.center_ = null; 1076 this.map_ = cluster.getMap(); 1077 this.div_ = null; 1078 this.sums_ = null; 1079 this.visible_ = false; 1080 1081 this.setMap(this.map_); 1082} 1083 1084 1085/** 1086 * Triggers the clusterclick event and zoom's if the option is set. 1087 */ 1088ClusterIcon.prototype.triggerClusterClick = function () { 1089 var markerClusterer = this.cluster_.getMarkerClusterer(); 1090 1091 // Trigger the clusterclick event. 1092 google.maps.event.trigger(markerClusterer.map_, 'clusterclick', this.cluster_); 1093 1094 if (markerClusterer.isZoomOnClick()) { 1095 // Zoom into the cluster. 1096 this.map_.fitBounds(this.cluster_.getBounds()); 1097 } 1098}; 1099 1100 1101/** 1102 * Adding the cluster icon to the dom. 1103 * @ignore 1104 */ 1105ClusterIcon.prototype.onAdd = function () { 1106 this.div_ = document.createElement('DIV'); 1107 if (this.visible_) { 1108 var pos = this.getPosFromLatLng_(this.center_); 1109 this.div_.style.cssText = this.createCss(pos); 1110 this.div_.innerHTML = this.sums_.text; 1111 } 1112 1113 var panes = this.getPanes(); 1114 panes.overlayMouseTarget.appendChild(this.div_); 1115 1116 var that = this; 1117 google.maps.event.addDomListener(this.div_, 'click', function () { 1118 that.triggerClusterClick(); 1119 }); 1120}; 1121 1122 1123/** 1124 * Returns the position to place the div dending on the latlng. 1125 * 1126 * @param {google.maps.LatLng} latlng The position in latlng. 1127 * @return {google.maps.Point} The position in pixels. 1128 * @private 1129 */ 1130ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) { 1131 var pos = this.getProjection().fromLatLngToDivPixel(latlng); 1132 pos.x -= parseInt(this.width_ / 2, 10); 1133 pos.y -= parseInt(this.height_ / 2, 10); 1134 return pos; 1135}; 1136 1137 1138/** 1139 * Draw the icon. 1140 * @ignore 1141 */ 1142ClusterIcon.prototype.draw = function () { 1143 if (this.visible_) { 1144 var pos = this.getPosFromLatLng_(this.center_); 1145 this.div_.style.top = pos.y + 'px'; 1146 this.div_.style.left = pos.x + 'px'; 1147 this.div_.style.zIndex = google.maps.Marker.MAX_ZINDEX + 1; 1148 } 1149}; 1150 1151 1152/** 1153 * Hide the icon. 1154 */ 1155ClusterIcon.prototype.hide = function () { 1156 if (this.div_) { 1157 this.div_.style.display = 'none'; 1158 } 1159 this.visible_ = false;
1160}; 1161 1162 1163/** 1164 * Position and show the icon. 1165 */ 1166ClusterIcon.prototype.show = function () { 1167 if (this.div_) { 1168 var pos = this.getPosFromLatLng_(this.center_); 1169 this.div_.style.cssText = this.createCss(pos); 1170 this.div_.style.display = ''; 1171 } 1172 this.visible_ = true; 1173}; 1174 1175 1176/** 1177 * Remove the icon from the map 1178 */ 1179ClusterIcon.prototype.remove = function () { 1180 this.setMap(null); 1181}; 1182 1183 1184/** 1185 * Implementation of the onRemove interface. 1186 * @ignore 1187 */ 1188ClusterIcon.prototype.onRemove = function () { 1189 if (this.div_ && this.div_.parentNode) { 1190 this.hide(); 1191 this.div_.parentNode.removeChild(this.div_); 1192 this.div_ = null; 1193 } 1194}; 1195 1196 1197/** 1198 * Set the sums of the icon. 1199 * 1200 * @param {Object} sums The sums containing: 1201 * 'text': (string) The text to display in the icon. 1202 * 'index': (number) The style index of the icon. 1203 */ 1204ClusterIcon.prototype.setSums = function (sums) { 1205 this.sums_ = sums; 1206 this.text_ = sums.text; 1207 this.index_ = sums.index; 1208 if (this.div_) { 1209 this.div_.innerHTML = sums.text; 1210 } 1211 1212 this.useStyle(); 1213}; 1214 1215 1216/** 1217 * Sets the icon to the the styles. 1218 */ 1219ClusterIcon.prototype.useStyle = function () { 1220 var index = Math.max(0, this.sums_.index - 1); 1221 index = Math.min(this.styles_.length - 1, index); 1222 var style = this.styles_[index]; 1223 this.url_ = style['url']; 1224 this.height_ = style['height']; 1225 this.width_ = style['width']; 1226 this.textColor_ = style['textColor']; 1227 this.anchor_ = style['anchor']; 1228 this.textSize_ = style['textSize']; 1229 this.backgroundPosition_ = style['backgroundPosition']; 1230}; 1231 1232 1233/** 1234 * Sets the center of the icon. 1235 * 1236 * @param {google.maps.LatLng} center The latlng to set as the center. 1237 */ 1238ClusterIcon.prototype.setCenter = function (center) { 1239 this.center_ = center; 1240}; 1241 1242 1243/** 1244 * Create the css text based on the position of the icon. 1245 * 1246 * @param {google.maps.Point} pos The position. 1247 * @return {string} The css style text. 1248 */ 1249ClusterIcon.prototype.createCss = function (pos) { 1250 var style = []; 1251 style.push('background-image:url(' + this.url_ + ');'); 1252 var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0'; 1253 style.push('background-position:' + backgroundPosition + ';'); 1254 1255 if (typeof this.anchor_ === 'object') { 1256 if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && 1257 this.anchor_[0] < this.height_) { 1258 style.push('height:' + (this.height_ - this.anchor_[0]) + 1259 'px; padding-top:' + this.anchor_[0] + 'px;'); 1260 } else { 1261 style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + 1262 'px;'); 1263 } 1264 if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && 1265 this.anchor_[1] < this.width_) { 1266 style.push('width:' + (this.width_ - this.anchor_[1]) + 1267 'px; padding-left:' + this.anchor_[1] + 'px;'); 1268 } else { 1269 style.push('width:' + this.width_ + 'px; text-align:center;'); 1270 } 1271 } else { 1272 style.push('height:' + this.height_ + 'px; line-height:' + 1273 this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); 1274 } 1275 1276 var txtColor = this.textColor_ ? this.textColor_ : 'black'; 1277 var txtSize = this.textSize_ ? this.textSize_ : 11; 1278 1279 style.push('cursor:pointer; top:' + pos.y + 'px; left:' + 1280 pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + 1281 txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold'); 1282 return style.join(''); 1283}; 1284 1285 1286// Export Symbols for Closure 1287// If you are not going to compile with closure then you can remove the 1288// code below. 1289var window = window || {}; 1290window['MarkerClusterer'] = MarkerClusterer; 1291MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker; 1292MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers; 1293MarkerClusterer.prototype['clearMarkers'] = 1294 MarkerClusterer.prototype.clearMarkers; 1295MarkerClusterer.prototype['fitMapToMarkers'] = 1296 MarkerClusterer.prototype.fitMapToMarkers; 1297MarkerClusterer.prototype['getCalculator'] = 1298 MarkerClusterer.prototype.getCalculator; 1299MarkerClusterer.prototype['getGridSize'] = 1300 MarkerClusterer.prototype.getGridSize; 1301MarkerClusterer.prototype['getExtendedBounds'] = 1302 MarkerClusterer.prototype.getExtendedBounds;
1303MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap; 1304MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers; 1305MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom; 1306MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles; 1307MarkerClusterer.prototype['getTotalClusters'] = 1308 MarkerClusterer.prototype.getTotalClusters; 1309MarkerClusterer.prototype['getTotalMarkers'] = 1310 MarkerClusterer.prototype.getTotalMarkers; 1311MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw; 1312MarkerClusterer.prototype['removeMarker'] = 1313 MarkerClusterer.prototype.removeMarker; 1314MarkerClusterer.prototype['removeMarkers'] = 1315 MarkerClusterer.prototype.removeMarkers; 1316MarkerClusterer.prototype['resetViewport'] = 1317 MarkerClusterer.prototype.resetViewport; 1318MarkerClusterer.prototype['repaint'] = 1319 MarkerClusterer.prototype.repaint; 1320MarkerClusterer.prototype['setCalculator'] = 1321 MarkerClusterer.prototype.setCalculator; 1322MarkerClusterer.prototype['setGridSize'] = 1323 MarkerClusterer.prototype.setGridSize; 1324MarkerClusterer.prototype['setMaxZoom'] = 1325 MarkerClusterer.prototype.setMaxZoom; 1326MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd; 1327MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw; 1328 1329Cluster.prototype['getCenter'] = Cluster.prototype.getCenter; 1330Cluster.prototype['getSize'] = Cluster.prototype.getSize; 1331Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers; 1332 1333ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd; 1334ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw; 1335ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove; 1336 1337Object.keys = Object.keys || function (o) { 1338 var result = []; 1339 for (var name in o) { 1340 if (o.hasOwnProperty(name)) 1341 result.push(name); 1342 } 1343 return result; 1344}; 1345 1346if (typeof module == 'object') { 1347 module.exports = MarkerClusterer; 1348}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.