PageSourceSearch

https://www.allerradweg.de/typo3conf/ext/ndsmaps/Resources/Public/Assets/Js/Components/Gmaps/addons.js

js allerradweg.de collected 2026-10-02 17:19:15 UTC 45,357 bytes, 1,348 lines download raw bytes

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=/(\?.*(&|&amp;)|\?)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.