PageSourceSearch

https://www.biopoleantilles.fr/wp-content/plugins/wp-google-map-pl…/markerclustererplus.js?ver=6.1.14

js biopoleantilles.fr collected 2026-10-07 01:09:14 UTC 53,315 bytes, 1,632 lines download raw bytes

1/**
2 * @name MarkerClustererPlus for Google Maps V3
3 * @version 2.1.2 [May 28, 2014]
4 * @author Gary Little
5 * @fileoverview
6 * The library creates and manages per-zoom-level clusters for large amounts of markers.
7 * <p>
8 * This is an enhanced V3 implementation of the
9 * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
10 * >V2 MarkerClusterer</a> by Xiaoxi Wu. It is based on the
11 * <a href="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/"
12 * >V3 MarkerClusterer</a> port by Luke Mahe. MarkerClustererPlus was created by Gary Little.
13 * <p>
14 * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It
15 *  adds support for the <code>ignoreHidden</code>, <code>title</code>, <code>batchSizeIE</code>,
16 *  and <code>calculator</code> properties as well as support for four more events. It also allows
17 *  greater control over the styling of the text that appears on the cluster marker. The
18 *  documentation has been significantly improved and the overall code has been simplified and
19 *  polished. Very large numbers of markers can now be managed without causing Javascript timeout
20 *  errors on Internet Explorer. Note that the name of the <code>clusterclick</code> event has been
21 *  deprecated. The new name is <code>click</code>, so please change your application code now.
22 */
23
24/**
25 * Licensed under the Apache License, Version 2.0 (the "License");
26 * you may not use this file except in compliance with the License.
27 * You may obtain a copy of the License at
28 *
29 *     http://www.apache.org/licenses/LICENSE-2.0
30 *
31 * Unless required by applicable law or agreed to in writing, software
32 * distributed under the License is distributed on an "AS IS" BASIS,
33 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34 * See the License for the specific language governing permissions and
35 * limitations under the License.
36 */
37
38
39/**
40 * @name ClusterIconStyle
41 * @class This class represents the object for values in the <code>styles</code> array passed
42 *  to the {@link MarkerClusterer} constructor. The element in this array that is used to
43 *  style the cluster icon is determined by calling the <code>calculator</code> function.
44 *
45 * @property {string} url The URL of the cluster icon image file. Required.
46 * @property {number} height The display height (in pixels) of the cluster icon. Required.
47 * @property {number} width The display width (in pixels) of the cluster icon. Required.
48 * @property {Array} [anchorText] The position (in pixels) from the center of the cluster icon to
49 *  where the text label is to be centered and drawn. The format is <code>[yoffset, xoffset]</code>
50 *  where <code>yoffset</code> increases as you go down from center and <code>xoffset</code>
51 *  increases to the right of center. The default is <code>[0, 0]</code>.
52 * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the
53 *  spot on the cluster icon that is to be aligned with the cluster position. The format is
54 *  <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and
55 *  <code>xoffset</code> increases to the right of the top-left corner of the icon. The default
56 *  anchor position is the center of the cluster icon.
57 * @property {string} [textColor="black"] The color of the label text shown on the
58 *  cluster icon.
59 * @property {number} [textSize=11] The size (in pixels) of the label text shown on the
60 *  cluster icon.
61 * @property {string} [textDecoration="none"] The value of the CSS <code>text-decoration</code>
62 *  property for the label text shown on the cluster icon.
63 * @property {string} [fontWeight="bold"] The value of the CSS <code>font-weight</code>
64 *  property for the label text shown on the cluster icon.
65 * @property {string} [fontStyle="normal"] The value of the CSS <code>font-style</code>
66 *  property for the label text shown on the cluster icon.
67 * @property {string} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code>
68 *  property for the label text shown on the cluster icon.
69 * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image
70 *  within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code>
71 *  (the same format as for the CSS <code>background-position</code> property). You must set
72 *  this property appropriately when the image defined by <code>url</code> represents a sprite
73 *  containing multiple images. Note that the position <i>must</i> be specified in px units.
74 */
75/**
76 * @name ClusterIconInfo
77 * @class This class is an object containing general information about a cluster icon. This is
78 *  the object that a <code>calculator</code> function returns.
79 *
80 * @property {string} text The text of the label to be shown on the cluster icon.
81 * @property {number} index The index plus 1 of the element in the <code>styles</code>
82 *  array to be used to style the cluster icon.
83 * @property {string} title The tooltip to display when the mouse moves over the cluster icon.
84 *  If this value is <code>undefined</code> or <code>""</code>, <code>title</code> is set to the
85 *  value of the <code>title</code> property passed to the MarkerClusterer.
86 */
87/**
88 * A cluster icon.
89 *
90 * @constructor
91 * @extends google.maps.OverlayView
92 * @param {Cluster} cluster The cluster with which the icon is to be associated.
93 * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons
94 *  to use for various cluster sizes.
95 * @private
96 */
97function ClusterIcon(cluster, styles) {
98    cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
99
100    this.cluster_ = cluster;
101    this.className_ = cluster.getMarkerClusterer().getClusterClass();
102    this.styles_ = styles;
103    this.center_ = null;
104    this.div_ = null;
105    this.sums_ = null;
106    this.visible_ = false;
107
108    this.setMap(cluster.getMap()); // Note: this causes onAdd to be called
109}
110
111
112/**
113 * Adds the icon to the DOM.
114 */
115ClusterIcon.prototype.onAdd = function() {
116    var cClusterIcon = this;
117    var cMouseDownInCluster;
118    var cDraggingMapByCluster;
119
120    this.div_ = document.createElement("div");
121    this.div_.className = this.className_;
122    if (this.visible_) {
123        this.show();
124    }
125
126    this.getPanes().overlayMouseTarget.appendChild(this.div_);
127
128    // Fix for Issue 157
129    this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function() {
130        cDraggingMapByCluster = cMouseDownInCluster;
131    });
132
133    google.maps.event.addDomListener(this.div_, "mousedown", function() {
134        cMouseDownInCluster = true;
135        cDraggingMapByCluster = false;
136    });
137
138    google.maps.event.addDomListener(this.div_, "click", function(e) {
139        cMouseDownInCluster = false;
140        if (!cDraggingMapByCluster) {
141            var theBounds;
142            var mz;
143            var mc = cClusterIcon.cluster_.getMarkerClusterer();
144            /**
145             * This event is fired when a cluster marker is clicked.
146             * @name MarkerClusterer#click
147             * @param {Cluster} c The cluster that was clicked.
148             * @event
149             */
150            google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
151            google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
152
153            // The default click handler follows. Disable it by setting
154            // the zoomOnClick property to false.
155            if (mc.getZoomOnClick()) {
156                // Zoom into the cluster.
157                mz = mc.getMaxZoom();
158                theBounds = cClusterIcon.cluster_.getBounds();
159                mc.getMap().fitBounds(theBounds);
160                // There is a fix for Issue 170 here:
161                setTimeout(function() {
162                    mc.getMap().fitBounds(theBounds);
163                    // Don't zoom beyond the max zoom level
164                    if (mz !== null && (mc.getMap().getZoom() > mz)) {
165                        mc.getMap().setZoom(mz + 1);
166                    }
167                }, 100);
168            }
169
170            // Prevent event propagation to the map:
171            e.cancelBubble = true;
172            if (e.stopPropagation) {
173                e.stopPropagation();
174            }
175        }
176    });
177
178    google.maps.event.addDomListener(this.div_, "mouseover", function() {
179        var mc = cClusterIcon.cluster_.getMarkerClusterer();
180        /**
181         * This event is fired when the mouse moves over a cluster marker.
182         * @name MarkerClusterer#mouseover
183         * @param {Cluster} c The cluster that the mouse moved over.
184         * @event
185         */
186        google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_);
187    });
188
189    google.maps.event.addDomListener(this.div_, "mouseout", function() {
190        var mc = cClusterIcon.cluster_.getMarkerClusterer();
191        /**
vendor: 14,931 bytes, lines 192-623
192         * This event is fired when the mouse moves out of a cluster marker.
193         * @name MarkerClusterer#mouseout
194         * @param {Cluster} c The cluster that the mouse moved out of.
195         * @event
196         */
197        google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_);
198    });
199};
200
201
202/**
203 * Removes the icon from the DOM.
204 */
205ClusterIcon.prototype.onRemove = function() {
206    if (this.div_ && this.div_.parentNode) {
207        this.hide();
208        google.maps.event.removeListener(this.boundsChangedListener_);
209        google.maps.event.clearInstanceListeners(this.div_);
210        this.div_.parentNode.removeChild(this.div_);
211        this.div_ = null;
212    }
213};
214
215
216/**
217 * Draws the icon.
218 */
219ClusterIcon.prototype.draw = function() {
220    if (this.visible_) {
221        var pos = this.getPosFromLatLng_(this.center_);
222        this.div_.style.top = pos.y + "px";
223        this.div_.style.left = pos.x + "px";
224    }
225};
226
227
228/**
229 * Hides the icon.
230 */
231ClusterIcon.prototype.hide = function() {
232    if (this.div_) {
233        this.div_.style.display = "none";
234    }
235    this.visible_ = false;
236};
237
238
239/**
240 * Positions and shows the icon.
241 */
242ClusterIcon.prototype.show = function() {
243    if (this.div_) {
244        var img = "";
245        // NOTE: values must be specified in px units
246        var bp = this.backgroundPosition_.split(" ");
247        var spriteH = parseInt(bp[0].replace(/^\s+|\s+$/g, ""), 10);
248        var spriteV = parseInt(bp[1].replace(/^\s+|\s+$/g, ""), 10);
249        var pos = this.getPosFromLatLng_(this.center_);
250        this.div_.style.cssText = this.createCss(pos);
251        img = "<img src='" + this.url_ + "' style='position: absolute; top: " + spriteV + "px; left: " + spriteH + "px; ";
252        if (!this.cluster_.getMarkerClusterer().enableRetinaIcons_) {
253            img += "clip: rect(" + (-1 * spriteV) + "px, " + ((-1 * spriteH) + this.width_) + "px, " +
254                ((-1 * spriteV) + this.height_) + "px, " + (-1 * spriteH) + "px);";
255        }
256        img += "'>";
257        this.div_.innerHTML = img + "<div style='" +
258            "position: absolute;" +
259            "top: " + this.anchorText_[0] + "px;" +
260            "left: " + this.anchorText_[1] + "px;" +
261            "color: " + this.textColor_ + ";" +
262            "font-size: " + this.textSize_ + "px;" +
263            "font-family: " + this.fontFamily_ + ";" +
264            "font-weight: " + this.fontWeight_ + ";" +
265            "font-style: " + this.fontStyle_ + ";" +
266            "text-decoration: " + this.textDecoration_ + ";" +
267            "text-align: center;" +
268            "width: " + this.width_ + "px;" +
269            "line-height:" + this.height_ + "px;" +
270            "'>" + this.sums_.text + "</div>";
271        if (typeof this.sums_.title === "undefined" || this.sums_.title === "") {
272            this.div_.title = this.cluster_.getMarkerClusterer().getTitle();
273        } else {
274            this.div_.title = this.sums_.title;
275        }
276        this.div_.style.display = "";
277    }
278    this.visible_ = true;
279};
280
281
282/**
283 * Sets the icon styles to the appropriate element in the styles array.
284 *
285 * @param {ClusterIconInfo} sums The icon label text and styles index.
286 */
287ClusterIcon.prototype.useStyle = function(sums) {
288    this.sums_ = sums;
289    var index = Math.max(0, sums.index - 1);
290    index = Math.min(this.styles_.length - 1, index);
291    var style = this.styles_[index];
292    this.url_ = style.url;
293    this.height_ = style.height;
294    this.width_ = style.width;
295    this.anchorText_ = style.anchorText || [0, 0];
296    this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)];
297    this.textColor_ = style.textColor || "black";
298    this.textSize_ = style.textSize || 11;
299    this.textDecoration_ = style.textDecoration || "none";
300    this.fontWeight_ = style.fontWeight || "bold";
301    this.fontStyle_ = style.fontStyle || "normal";
302    this.fontFamily_ = style.fontFamily || "Arial,sans-serif";
303    this.backgroundPosition_ = style.backgroundPosition || "0 0";
304};
305
306
307/**
308 * Sets the position at which to center the icon.
309 *
310 * @param {google.maps.LatLng} center The latlng to set as the center.
311 */
312ClusterIcon.prototype.setCenter = function(center) {
313    this.center_ = center;
314};
315
316
317/**
318 * Creates the cssText style parameter based on the position of the icon.
319 *
320 * @param {google.maps.Point} pos The position of the icon.
321 * @return {string} The CSS style text.
322 */
323ClusterIcon.prototype.createCss = function(pos) {
324    var style = [];
325    style.push("cursor: pointer;");
326    style.push("position: absolute; top: " + pos.y + "px; left: " + pos.x + "px;");
327    style.push("width: " + this.width_ + "px; height: " + this.height_ + "px;");
328    return style.join("");
329};
330
331
332/**
333 * Returns the position at which to place the DIV depending on the latlng.
334 *
335 * @param {google.maps.LatLng} latlng The position in latlng.
336 * @return {google.maps.Point} The position in pixels.
337 */
338ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) {
339    var pos = this.getProjection().fromLatLngToDivPixel(latlng);
340    pos.x -= this.anchorIcon_[1];
341    pos.y -= this.anchorIcon_[0];
342    pos.x = parseInt(pos.x, 10);
343    pos.y = parseInt(pos.y, 10);
344    return pos;
345};
346
347
348/**
349 * Creates a single cluster that manages a group of proximate markers.
350 *  Used internally, do not call this constructor directly.
351 * @constructor
352 * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this
353 *  cluster is associated.
354 */
355function Cluster(mc) {
356    this.markerClusterer_ = mc;
357    this.map_ = mc.getMap();
358    this.gridSize_ = mc.getGridSize();
359    this.minClusterSize_ = mc.getMinimumClusterSize();
360    this.averageCenter_ = mc.getAverageCenter();
361    this.markers_ = [];
362    this.center_ = null;
363    this.bounds_ = null;
364    this.clusterIcon_ = new ClusterIcon(this, mc.getStyles());
365}
366
367
368/**
369 * Returns the number of markers managed by the cluster. You can call this from
370 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
371 * for the <code>MarkerClusterer</code> object.
372 *
373 * @return {number} The number of markers in the cluster.
374 */
375Cluster.prototype.getSize = function() {
376    return this.markers_.length;
377};
378
379
380/**
381 * Returns the array of markers managed by the cluster. You can call this from
382 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
383 * for the <code>MarkerClusterer</code> object.
384 *
385 * @return {Array} The array of markers in the cluster.
386 */
387Cluster.prototype.getMarkers = function() {
388    return this.markers_;
389};
390
391
392/**
393 * Returns the center of the cluster. You can call this from
394 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
395 * for the <code>MarkerClusterer</code> object.
396 *
397 * @return {google.maps.LatLng} The center of the cluster.
398 */
399Cluster.prototype.getCenter = function() {
400    return this.center_;
401};
402
403
404/**
405 * Returns the map with which the cluster is associated.
406 *
407 * @return {google.maps.Map} The map.
408 * @ignore
409 */
410Cluster.prototype.getMap = function() {
411    return this.map_;
412};
413
414
415/**
416 * Returns the <code>MarkerClusterer</code> object with which the cluster is associated.
417 *
418 * @return {MarkerClusterer} The associated marker clusterer.
419 * @ignore
420 */
421Cluster.prototype.getMarkerClusterer = function() {
422    return this.markerClusterer_;
423};
424
425
426/**
427 * Returns the bounds of the cluster.
428 *
429 * @return {google.maps.LatLngBounds} the cluster bounds.
430 * @ignore
431 */
432Cluster.prototype.getBounds = function() {
433    var i;
434    var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
435    var markers = this.getMarkers();
436    for (i = 0; i < markers.length; i++) {
437        bounds.extend(markers[i].getPosition());
438    }
439    return bounds;
440};
441
442
443/**
444 * Removes the cluster from the map.
445 *
446 * @ignore
447 */
448Cluster.prototype.remove = function() {
449    this.clusterIcon_.setMap(null);
450    this.markers_ = [];
451    delete this.markers_;
452};
453
454
455/**
456 * Adds a marker to the cluster.
457 *
458 * @param {google.maps.Marker} marker The marker to be added.
459 * @return {boolean} True if the marker was added.
460 * @ignore
461 */
462Cluster.prototype.addMarker = function(marker) {
463    var i;
464    var mCount;
465    var mz;
466
467    if (this.isMarkerAlreadyAdded_(marker)) {
468        return false;
469    }
470
471    if (!this.center_) {
472        this.center_ = marker.getPosition();
473        this.calculateBounds_();
474    } else {
475        if (this.averageCenter_) {
476            var l = this.markers_.length + 1;
477            var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l;
478            var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l;
479            this.center_ = new google.maps.LatLng(lat, lng);
480            this.calculateBounds_();
481        }
482    }
483
484    marker.isAdded = true;
485    this.markers_.push(marker);
486
487    mCount = this.markers_.length;
488    mz = this.markerClusterer_.getMaxZoom();
489    if (mz !== null && this.map_.getZoom() > mz) {
490        // Zoomed in past max zoom, so show the marker.
491        if (marker.getMap() !== this.map_) {
492            marker.setMap(this.map_);
493        }
494    } else if (mCount < this.minClusterSize_) {
495        // Min cluster size not reached so show the marker.
496        if (marker.getMap() !== this.map_) {
497            marker.setMap(this.map_);
498        }
499    } else if (mCount === this.minClusterSize_) {
500        // Hide the markers that were showing.
501        for (i = 0; i < mCount; i++) {
502            this.markers_[i].setMap(null);
503        }
504    } else {
505        marker.setMap(null);
506    }
507
508    this.updateIcon_();
509    return true;
510};
511
512
513/**
514 * Determines if a marker lies within the cluster's bounds.
515 *
516 * @param {google.maps.Marker} marker The marker to check.
517 * @return {boolean} True if the marker lies in the bounds.
518 * @ignore
519 */
520Cluster.prototype.isMarkerInClusterBounds = function(marker) {
521    return this.bounds_.contains(marker.getPosition());
522};
523
524
525/**
526 * Calculates the extended bounds of the cluster with the grid.
527 */
528Cluster.prototype.calculateBounds_ = function() {
529    var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
530    this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
531};
532
533
534/**
535 * Updates the cluster icon.
536 */
537Cluster.prototype.updateIcon_ = function() {
538    var mCount = this.markers_.length;
539    var mz = this.markerClusterer_.getMaxZoom();
540
541    if (mz !== null && this.map_.getZoom() > mz) {
542        this.clusterIcon_.hide();
543        return;
544    }
545
546    if (mCount < this.minClusterSize_) {
547        // Min cluster size not yet reached.
548        this.clusterIcon_.hide();
549        return;
550    }
551
552    var numStyles = this.markerClusterer_.getStyles().length;
553    var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
554    this.clusterIcon_.setCenter(this.center_);
555    this.clusterIcon_.useStyle(sums);
556    this.clusterIcon_.show();
557};
558
559
560/**
561 * Determines if a marker has already been added to the cluster.
562 *
563 * @param {google.maps.Marker} marker The marker to check.
564 * @return {boolean} True if the marker has already been added.
565 */
566Cluster.prototype.isMarkerAlreadyAdded_ = function(marker) {
567    var i;
568    if (this.markers_.indexOf) {
569        return this.markers_.indexOf(marker) !== -1;
570    } else {
571        for (i = 0; i < this.markers_.length; i++) {
572            if (marker === this.markers_[i]) {
573                return true;
574            }
575        }
576    }
577    return false;
578};
579
580
581/**
582 * @name MarkerClustererOptions
583 * @class This class represents the optional parameter passed to
584 *  the {@link MarkerClusterer} constructor.
585 * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square.
586 * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or
587 *  <code>null</code> if clustering is to be enabled at all zoom levels.
588 * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is
589 *  clicked. You may want to set this to <code>false</code> if you have installed a handler
590 *  for the <code>click</code> event and it deals with zooming on its own.
591 * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be
592 *  the average position of all markers in the cluster. If set to <code>false</code>, the
593 *  cluster marker is positioned at the location of the first marker added to the cluster.
594 * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster
595 *  before the markers are hidden and a cluster marker appears.
596 * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You
597 *  may want to set this to <code>true</code> to ensure that hidden markers are not included
598 *  in the marker count that appears on a cluster marker (this count is the value of the
599 *  <code>text</code> property of the result returned by the default <code>calculator</code>).
600 *  If set to <code>true</code> and you change the visibility of a marker being clustered, be
601 *  sure to also call <code>MarkerClusterer.repaint()</code>.
602 * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster
603 *  marker. (Alternatively, you can use a custom <code>calculator</code> function to specify a
604 *  different tooltip for each cluster marker.)
605 * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine
606 *  the text to be displayed on a cluster marker and the index indicating which style to use
607 *  for the cluster marker. The input parameters for the function are (1) the array of markers
608 *  represented by a cluster marker and (2) the number of cluster icon styles. It returns a
609 *  {@link ClusterIconInfo} object. The default <code>calculator</code> returns a
610 *  <code>text</code> property which is the number of markers in the cluster and an
611 *  <code>index</code> property which is one higher than the lowest integer such that
612 *  <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles
613 *  array, whichever is less. The <code>styles</code> array element used has an index of
614 *  <code>index</code> minus 1. For example, the default <code>calculator</code> returns a
615 *  <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code>
616 *  for a cluster icon representing 125 markers so the element used in the <code>styles</code>
617 *  array is <code>2</code>. A <code>calculator</code> may also return a <code>title</code>
618 *  property that contains the text of the tooltip to be used for the cluster marker. If
619 *   <code>title</code> is not defined, the tooltip is set to the value of the <code>title</code>
620 *   property for the MarkerClusterer.
621 * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles
622 *  for the cluster markers. Use this class to define CSS styles that are not set up by the code
623 *  that processes the <code>styles</code> array.
624 * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles
625 *  of the cluster markers to be used. The element to be used to style a given cluster marker
626 *  is determined by the function defined by the <code>calculator</code> property.
627 *  The default is an array of {@link ClusterIconStyle} elements whose properties are derived
628 *  from the values for <code>imagePath</code>, <code>imageExtension</code>, and
629 *  <code>imageSizes</code>.
630 * @property {boolean} [enableRetinaIcons=false] Whether to allow the use of cluster icons that
631 * have sizes that are some multiple (typically double) of their actual display size. Icons such
632 * as these look better when viewed on high-resolution monitors such as Apple's Retina displays.
633 * Note: if this property is <code>true</code>, sprites cannot be used as cluster icons.
634 * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the
635 *  number of markers to be processed in a single batch when using a browser other than
636 *  Internet Explorer (for Internet Explorer, use the batchSizeIE property instead).
637 * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is
638 *  being used, markers are processed in several batches with a small delay inserted between
639 *  each batch in an attempt to avoid Javascript timeout errors. Set this property to the
640 *  number of markers to be processed in a single batch; select as high a number as you can
641 *  without causing a timeout error in the browser. This number might need to be as low as 100
642 *  if 15,000 markers are being managed, for example.
643 * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH]
644 *  The full URL of the root name of the group of image files to use for cluster icons.
645 *  The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code>
646 *  where n is the image file number (1, 2, etc.).
647 * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION]
648 *  The extension name for the cluster icon image files (e.g., <code>"png"</code> or
649 *  <code>"jpg"</code>).
650 * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES]
651 *  An array of numbers containing the widths of the group of
652 *  <code>imagePath</code>n.<code>imageExtension</code> image files.
653 *  (The images are assumed to be square.)
654 */
655/**
656 * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}.
657 * @constructor
658 * @extends google.maps.OverlayView
659 * @param {google.maps.Map} map The Google map to attach to.
660 * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster.
661 * @param {MarkerClustererOptions} [opt_options] The optional parameters.
662 */
663function MarkerClusterer(map, opt_markers, opt_options) {
664    // MarkerClusterer implements google.maps.OverlayView interface. We use the
665    // extend function to extend MarkerClusterer with google.maps.OverlayView
666    // because it might not always be available when the code is defined so we
667    // look for it at the last possible moment. If it doesn't exist now then
668    // there is no point going ahead :)
669    this.extend(MarkerClusterer, google.maps.OverlayView);
670
671    opt_markers = opt_markers || [];
672    opt_options = opt_options || {};
673
674    this.markers_ = [];
675    this.clusters_ = [];
676    this.listeners_ = [];
677    this.activeMap_ = null;
678    this.ready_ = false;
679
680    this.gridSize_ = opt_options.gridSize || 60;
681    this.minClusterSize_ = opt_options.minimumClusterSize || 2;
682    this.maxZoom_ = opt_options.maxZoom || null;
683    this.styles_ = opt_options.styles || [];
684    this.title_ = opt_options.title || "";
685    this.zoomOnClick_ = true;
686    if (opt_options.zoomOnClick !== undefined) {
687        this.zoomOnClick_ = opt_options.zoomOnClick;
688    }
689    this.averageCenter_ = false;
690    if (opt_options.averageCenter !== undefined) {
691        this.averageCenter_ = opt_options.averageCenter;
692    }
693    this.ignoreHidden_ = false;
694    if (opt_options.ignoreHidden !== undefined) {
695        this.ignoreHidden_ = opt_options.ignoreHidden;
696    }
697    this.enableRetinaIcons_ = false;
vendor: 15,608 bytes, lines 698-1292
698    if (opt_options.enableRetinaIcons !== undefined) {
699        this.enableRetinaIcons_ = opt_options.enableRetinaIcons;
700    }
701    this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH;
702    this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION;
703    this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES;
704    this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR;
705    this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE;
706    this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE;
707    this.clusterClass_ = opt_options.clusterClass || "cluster";
708
709    if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) {
710        // Try to avoid IE timeout when processing a huge number of markers:
711        this.batchSize_ = this.batchSizeIE_;
712    }
713
714    this.setupStyles_();
715
716    this.addMarkers(opt_markers, true);
717    this.setMap(map); // Note: this causes onAdd to be called
718}
719
720
721/**
722 * Implementation of the onAdd interface method.
723 * @ignore
724 */
725MarkerClusterer.prototype.onAdd = function() {
726    var cMarkerClusterer = this;
727
728    this.activeMap_ = this.getMap();
729    this.ready_ = true;
730
731    this.repaint();
732
733    // Add the map event listeners
734    this.listeners_ = [
735        google.maps.event.addListener(this.getMap(), "zoom_changed", function() {
736            cMarkerClusterer.resetViewport_(false);
737            // Workaround for this Google bug: when map is at level 0 and "-" of
738            // zoom slider is clicked, a "zoom_changed" event is fired even though
739            // the map doesn't zoom out any further. In this situation, no "idle"
740            // event is triggered so the cluster markers that have been removed
741            // do not get redrawn. Same goes for a zoom in at maxZoom.
742            if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) {
743                google.maps.event.trigger(this, "idle");
744            }
745        }),
746        google.maps.event.addListener(this.getMap(), "idle", function() {
747            cMarkerClusterer.redraw_();
748        })
749    ];
750};
751
752
753/**
754 * Implementation of the onRemove interface method.
755 * Removes map event listeners and all cluster icons from the DOM.
756 * All managed markers are also put back on the map.
757 * @ignore
758 */
759MarkerClusterer.prototype.onRemove = function() {
760    var i;
761
762    // Put all the managed markers back on the map:
763    for (i = 0; i < this.markers_.length; i++) {
764        if (this.markers_[i].getMap() !== this.activeMap_) {
765            this.markers_[i].setMap(this.activeMap_);
766        }
767    }
768
769    // Remove all clusters:
770    for (i = 0; i < this.clusters_.length; i++) {
771        this.clusters_[i].remove();
772    }
773    this.clusters_ = [];
774
775    // Remove map event listeners:
776    for (i = 0; i < this.listeners_.length; i++) {
777        google.maps.event.removeListener(this.listeners_[i]);
778    }
779    this.listeners_ = [];
780
781    this.activeMap_ = null;
782    this.ready_ = false;
783};
784
785
786/**
787 * Implementation of the draw interface method.
788 * @ignore
789 */
790MarkerClusterer.prototype.draw = function() {};
791
792
793/**
794 * Sets up the styles object.
795 */
796MarkerClusterer.prototype.setupStyles_ = function() {
797    var i, size;
798    if (this.styles_.length > 0) {
799        return;
800    }
801
802    for (i = 0; i < this.imageSizes_.length; i++) {
803        size = this.imageSizes_[i];
804        this.styles_.push({
805            url: this.imagePath_ + (i + 1) + "." + this.imageExtension_,
806            height: size,
807            width: size
808        });
809    }
810};
811
812
813/**
814 *  Fits the map to the bounds of the markers managed by the clusterer.
815 */
816MarkerClusterer.prototype.fitMapToMarkers = function() {
817    var i;
818    var markers = this.getMarkers();
819    var bounds = new google.maps.LatLngBounds();
820    for (i = 0; i < markers.length; i++) {
821        bounds.extend(markers[i].getPosition());
822    }
823
824    this.getMap().fitBounds(bounds);
825};
826
827
828/**
829 * Returns the value of the <code>gridSize</code> property.
830 *
831 * @return {number} The grid size.
832 */
833MarkerClusterer.prototype.getGridSize = function() {
834    return this.gridSize_;
835};
836
837
838/**
839 * Sets the value of the <code>gridSize</code> property.
840 *
841 * @param {number} gridSize The grid size.
842 */
843MarkerClusterer.prototype.setGridSize = function(gridSize) {
844    this.gridSize_ = gridSize;
845};
846
847
848/**
849 * Returns the value of the <code>minimumClusterSize</code> property.
850 *
851 * @return {number} The minimum cluster size.
852 */
853MarkerClusterer.prototype.getMinimumClusterSize = function() {
854    return this.minClusterSize_;
855};
856
857/**
858 * Sets the value of the <code>minimumClusterSize</code> property.
859 *
860 * @param {number} minimumClusterSize The minimum cluster size.
861 */
862MarkerClusterer.prototype.setMinimumClusterSize = function(minimumClusterSize) {
863    this.minClusterSize_ = minimumClusterSize;
864};
865
866
867/**
868 *  Returns the value of the <code>maxZoom</code> property.
869 *
870 *  @return {number} The maximum zoom level.
871 */
872MarkerClusterer.prototype.getMaxZoom = function() {
873    return this.maxZoom_;
874};
875
876
877/**
878 *  Sets the value of the <code>maxZoom</code> property.
879 *
880 *  @param {number} maxZoom The maximum zoom level.
881 */
882MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
883    this.maxZoom_ = maxZoom;
884};
885
886
887/**
888 *  Returns the value of the <code>styles</code> property.
889 *
890 *  @return {Array} The array of styles defining the cluster markers to be used.
891 */
892MarkerClusterer.prototype.getStyles = function() {
893    return this.styles_;
894};
895
896
897/**
898 *  Sets the value of the <code>styles</code> property.
899 *
900 *  @param {Array.<ClusterIconStyle>} styles The array of styles to use.
901 */
902MarkerClusterer.prototype.setStyles = function(styles) {
903    this.styles_ = styles;
904};
905
906
907/**
908 * Returns the value of the <code>title</code> property.
909 *
910 * @return {string} The content of the title text.
911 */
912MarkerClusterer.prototype.getTitle = function() {
913    return this.title_;
914};
915
916
917/**
918 *  Sets the value of the <code>title</code> property.
919 *
920 *  @param {string} title The value of the title property.
921 */
922MarkerClusterer.prototype.setTitle = function(title) {
923    this.title_ = title;
924};
925
926
927/**
928 * Returns the value of the <code>zoomOnClick</code> property.
929 *
930 * @return {boolean} True if zoomOnClick property is set.
931 */
932MarkerClusterer.prototype.getZoomOnClick = function() {
933    return this.zoomOnClick_;
934};
935
936
937/**
938 *  Sets the value of the <code>zoomOnClick</code> property.
939 *
940 *  @param {boolean} zoomOnClick The value of the zoomOnClick property.
941 */
942MarkerClusterer.prototype.setZoomOnClick = function(zoomOnClick) {
943    this.zoomOnClick_ = zoomOnClick;
944};
945
946
947/**
948 * Returns the value of the <code>averageCenter</code> property.
949 *
950 * @return {boolean} True if averageCenter property is set.
951 */
952MarkerClusterer.prototype.getAverageCenter = function() {
953    return this.averageCenter_;
954};
955
956
957/**
958 *  Sets the value of the <code>averageCenter</code> property.
959 *
960 *  @param {boolean} averageCenter The value of the averageCenter property.
961 */
962MarkerClusterer.prototype.setAverageCenter = function(averageCenter) {
963    this.averageCenter_ = averageCenter;
964};
965
966
967/**
968 * Returns the value of the <code>ignoreHidden</code> property.
969 *
970 * @return {boolean} True if ignoreHidden property is set.
971 */
972MarkerClusterer.prototype.getIgnoreHidden = function() {
973    return this.ignoreHidden_;
974};
975
976
977/**
978 *  Sets the value of the <code>ignoreHidden</code> property.
979 *
980 *  @param {boolean} ignoreHidden The value of the ignoreHidden property.
981 */
982MarkerClusterer.prototype.setIgnoreHidden = function(ignoreHidden) {
983    this.ignoreHidden_ = ignoreHidden;
984};
985
986
987/**
988 * Returns the value of the <code>enableRetinaIcons</code> property.
989 *
990 * @return {boolean} True if enableRetinaIcons property is set.
991 */
992MarkerClusterer.prototype.getEnableRetinaIcons = function() {
993    return this.enableRetinaIcons_;
994};
995
996
997/**
998 *  Sets the value of the <code>enableRetinaIcons</code> property.
999 *
1000 *  @param {boolean} enableRetinaIcons The value of the enableRetinaIcons property.
1001 */
1002MarkerClusterer.prototype.setEnableRetinaIcons = function(enableRetinaIcons) {
1003    this.enableRetinaIcons_ = enableRetinaIcons;
1004};
1005
1006
1007/**
1008 * Returns the value of the <code>imageExtension</code> property.
1009 *
1010 * @return {string} The value of the imageExtension property.
1011 */
1012MarkerClusterer.prototype.getImageExtension = function() {
1013    return this.imageExtension_;
1014};
1015
1016
1017/**
1018 *  Sets the value of the <code>imageExtension</code> property.
1019 *
1020 *  @param {string} imageExtension The value of the imageExtension property.
1021 */
1022MarkerClusterer.prototype.setImageExtension = function(imageExtension) {
1023    this.imageExtension_ = imageExtension;
1024};
1025
1026
1027/**
1028 * Returns the value of the <code>imagePath</code> property.
1029 *
1030 * @return {string} The value of the imagePath property.
1031 */
1032MarkerClusterer.prototype.getImagePath = function() {
1033    return this.imagePath_;
1034};
1035
1036
1037/**
1038 *  Sets the value of the <code>imagePath</code> property.
1039 *
1040 *  @param {string} imagePath The value of the imagePath property.
1041 */
1042MarkerClusterer.prototype.setImagePath = function(imagePath) {
1043    this.imagePath_ = imagePath;
1044};
1045
1046
1047/**
1048 * Returns the value of the <code>imageSizes</code> property.
1049 *
1050 * @return {Array} The value of the imageSizes property.
1051 */
1052MarkerClusterer.prototype.getImageSizes = function() {
1053    return this.imageSizes_;
1054};
1055
1056
1057/**
1058 *  Sets the value of the <code>imageSizes</code> property.
1059 *
1060 *  @param {Array} imageSizes The value of the imageSizes property.
1061 */
1062MarkerClusterer.prototype.setImageSizes = function(imageSizes) {
1063    this.imageSizes_ = imageSizes;
1064};
1065
1066
1067/**
1068 * Returns the value of the <code>calculator</code> property.
1069 *
1070 * @return {function} the value of the calculator property.
1071 */
1072MarkerClusterer.prototype.getCalculator = function() {
1073    return this.calculator_;
1074};
1075
1076
1077/**
1078 * Sets the value of the <code>calculator</code> property.
1079 *
1080 * @param {function(Array.<google.maps.Marker>, number)} calculator The value
1081 *  of the calculator property.
1082 */
1083MarkerClusterer.prototype.setCalculator = function(calculator) {
1084    this.calculator_ = calculator;
1085};
1086
1087
1088/**
1089 * Returns the value of the <code>batchSizeIE</code> property.
1090 *
1091 * @return {number} the value of the batchSizeIE property.
1092 */
1093MarkerClusterer.prototype.getBatchSizeIE = function() {
1094    return this.batchSizeIE_;
1095};
1096
1097
1098/**
1099 * Sets the value of the <code>batchSizeIE</code> property.
1100 *
1101 *  @param {number} batchSizeIE The value of the batchSizeIE property.
1102 */
1103MarkerClusterer.prototype.setBatchSizeIE = function(batchSizeIE) {
1104    this.batchSizeIE_ = batchSizeIE;
1105};
1106
1107
1108/**
1109 * Returns the value of the <code>clusterClass</code> property.
1110 *
1111 * @return {string} the value of the clusterClass property.
1112 */
1113MarkerClusterer.prototype.getClusterClass = function() {
1114    return this.clusterClass_;
1115};
1116
1117
1118/**
1119 * Sets the value of the <code>clusterClass</code> property.
1120 *
1121 *  @param {string} clusterClass The value of the clusterClass property.
1122 */
1123MarkerClusterer.prototype.setClusterClass = function(clusterClass) {
1124    this.clusterClass_ = clusterClass;
1125};
1126
1127
1128/**
1129 *  Returns the array of markers managed by the clusterer.
1130 *
1131 *  @return {Array} The array of markers managed by the clusterer.
1132 */
1133MarkerClusterer.prototype.getMarkers = function() {
1134    return this.markers_;
1135};
1136
1137
1138/**
1139 *  Returns the number of markers managed by the clusterer.
1140 *
1141 *  @return {number} The number of markers.
1142 */
1143MarkerClusterer.prototype.getTotalMarkers = function() {
1144    return this.markers_.length;
1145};
1146
1147
1148/**
1149 * Returns the current array of clusters formed by the clusterer.
1150 *
1151 * @return {Array} The array of clusters formed by the clusterer.
1152 */
1153MarkerClusterer.prototype.getClusters = function() {
1154    return this.clusters_;
1155};
1156
1157
1158/**
1159 * Returns the number of clusters formed by the clusterer.
1160 *
1161 * @return {number} The number of clusters formed by the clusterer.
1162 */
1163MarkerClusterer.prototype.getTotalClusters = function() {
1164    return this.clusters_.length;
1165};
1166
1167
1168/**
1169 * Adds a marker to the clusterer. The clusters are redrawn unless
1170 *  <code>opt_nodraw</code> is set to <code>true</code>.
1171 *
1172 * @param {google.maps.Marker} marker The marker to add.
1173 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1174 */
1175MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) {
1176    this.pushMarkerTo_(marker);
1177    if (!opt_nodraw) {
1178        this.redraw_();
1179    }
1180};
1181
1182
1183/**
1184 * Adds an array of markers to the clusterer. The clusters are redrawn unless
1185 *  <code>opt_nodraw</code> is set to <code>true</code>.
1186 *
1187 * @param {Array.<google.maps.Marker>} markers The markers to add.
1188 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1189 */
1190MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) {
1191    var key;
1192    for (key in markers) {
1193        if (markers.hasOwnProperty(key)) {
1194            this.pushMarkerTo_(markers[key]);
1195        }
1196    }
1197    if (!opt_nodraw) {
1198        this.redraw_();
1199    }
1200};
1201
1202
1203/**
1204 * Pushes a marker to the clusterer.
1205 *
1206 * @param {google.maps.Marker} marker The marker to add.
1207 */
1208MarkerClusterer.prototype.pushMarkerTo_ = function(marker) {
1209    // If the marker is draggable add a listener so we can update the clusters on the dragend:
1210    if (marker.getDraggable()) {
1211        var cMarkerClusterer = this;
1212        google.maps.event.addListener(marker, "dragend", function() {
1213            if (cMarkerClusterer.ready_) {
1214                this.isAdded = false;
1215                cMarkerClusterer.repaint();
1216            }
1217        });
1218    }
1219    marker.isAdded = false;
1220    this.markers_.push(marker);
1221};
1222
1223
1224/**
1225 * Removes a marker from the cluster.  The clusters are redrawn unless
1226 *  <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the
1227 *  marker was removed from the clusterer.
1228 *
1229 * @param {google.maps.Marker} marker The marker to remove.
1230 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1231 * @return {boolean} True if the marker was removed from the clusterer.
1232 */
1233MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) {
1234    var removed = this.removeMarker_(marker);
1235
1236    if (!opt_nodraw && removed) {
1237        this.repaint();
1238    }
1239
1240    return removed;
1241};
1242
1243
1244/**
1245 * Removes an array of markers from the cluster. The clusters are redrawn unless
1246 *  <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers
1247 *  were removed from the clusterer.
1248 *
1249 * @param {Array.<google.maps.Marker>} markers The markers to remove.
1250 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1251 * @return {boolean} True if markers were removed from the clusterer.
1252 */
1253MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) {
1254    var i, r;
1255    var removed = false;
1256
1257    for (i = 0; i < markers.length; i++) {
1258        r = this.removeMarker_(markers[i]);
1259        removed = removed || r;
1260    }
1261
1262    if (!opt_nodraw && removed) {
1263        this.repaint();
1264    }
1265
1266    return removed;
1267};
1268
1269
1270/**
1271 * Removes a marker and returns true if removed, false if not.
1272 *
1273 * @param {google.maps.Marker} marker The marker to remove
1274 * @return {boolean} Whether the marker was removed or not
1275 */
1276MarkerClusterer.prototype.removeMarker_ = function(marker) {
1277    var i;
1278    var index = -1;
1279    if (this.markers_.indexOf) {
1280        index = this.markers_.indexOf(marker);
1281    } else {
1282        for (i = 0; i < this.markers_.length; i++) {
1283            if (marker === this.markers_[i]) {
1284                index = i;
1285                break;
1286            }
1287        }
1288    }
1289
1290    if (index === -1) {
1291        // Marker is not in our list of markers, so do nothing:
1292        return false;
1293    }
1294
1295    marker.setMap(null);
1296    this.markers_.splice(index, 1); // Remove the marker from the list of managed markers
1297    return true;
1298};
1299
1300
1301/**
1302 * Removes all clusters and markers from the map and also removes all markers
1303 *  managed by the clusterer.
1304 */
1305MarkerClusterer.prototype.clearMarkers = function() {
1306    this.resetViewport_(true);
1307    this.markers_ = [];
1308};
1309
1310
1311/**
1312 * Recalculates and redraws all the marker clusters from scratch.
1313 *  Call this after changing any properties.
1314 */
1315MarkerClusterer.prototype.repaint = function() {
1316    var oldClusters = this.clusters_.slice();
1317    this.clusters_ = [];
1318    this.resetViewport_(false);
1319    this.redraw_();
1320
1321    // Remove the old clusters.
1322    // Do it in a timeout to prevent blinking effect.
1323    setTimeout(function() {
1324        var i;
1325        for (i = 0; i < oldClusters.length; i++) {
1326            oldClusters[i].remove();
1327        }
1328    }, 0);
1329};
1330
1331
1332/**
1333 * Returns the current bounds extended by the grid size.
1334 *
1335 * @param {google.maps.LatLngBounds} bounds The bounds to extend.
1336 * @return {google.maps.LatLngBounds}
vendor: 8,648 bytes, lines 1336-1632
1336 The extended bounds.
1337 * @ignore
1338 */
1339MarkerClusterer.prototype.getExtendedBounds = function(bounds) {
1340    var projection = this.getProjection();
1341
1342    // Turn the bounds into latlng.
1343    var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
1344        bounds.getNorthEast().lng());
1345    var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
1346        bounds.getSouthWest().lng());
1347
1348    // Convert the points to pixels and the extend out by the grid size.
1349    var trPix = projection.fromLatLngToDivPixel(tr);
1350    trPix.x += this.gridSize_;
1351    trPix.y -= this.gridSize_;
1352
1353    var blPix = projection.fromLatLngToDivPixel(bl);
1354    blPix.x -= this.gridSize_;
1355    blPix.y += this.gridSize_;
1356
1357    // Convert the pixel points back to LatLng
1358    var ne = projection.fromDivPixelToLatLng(trPix);
1359    var sw = projection.fromDivPixelToLatLng(blPix);
1360
1361    // Extend the bounds to contain the new bounds.
1362    bounds.extend(ne);
1363    bounds.extend(sw);
1364
1365    return bounds;
1366};
1367
1368
1369/**
1370 * Redraws all the clusters.
1371 */
1372MarkerClusterer.prototype.redraw_ = function() {
1373    this.createClusters_(0);
1374};
1375
1376
1377/**
1378 * Removes all clusters from the map. The markers are also removed from the map
1379 *  if <code>opt_hide</code> is set to <code>true</code>.
1380 *
1381 * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers
1382 *  from the map.
1383 */
1384MarkerClusterer.prototype.resetViewport_ = function(opt_hide) {
1385    var i, marker;
1386    // Remove all the clusters
1387    for (i = 0; i < this.clusters_.length; i++) {
1388        this.clusters_[i].remove();
1389    }
1390    this.clusters_ = [];
1391
1392    // Reset the markers to not be added and to be removed from the map.
1393    for (i = 0; i < this.markers_.length; i++) {
1394        marker = this.markers_[i];
1395        marker.isAdded = false;
1396        if (opt_hide) {
1397            marker.setMap(null);
1398        }
1399    }
1400};
1401
1402
1403/**
1404 * Calculates the distance between two latlng locations in km.
1405 *
1406 * @param {google.maps.LatLng} p1 The first lat lng point.
1407 * @param {google.maps.LatLng} p2 The second lat lng point.
1408 * @return {number} The distance between the two points in km.
1409 * @see http://www.movable-type.co.uk/scripts/latlong.html
1410 */
1411MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) {
1412    var R = 6371; // Radius of the Earth in km
1413    var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
1414    var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
1415    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
1416        Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
1417        Math.sin(dLon / 2) * Math.sin(dLon / 2);
1418    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1419    var d = R * c;
1420    return d;
1421};
1422
1423
1424/**
1425 * Determines if a marker is contained in a bounds.
1426 *
1427 * @param {google.maps.Marker} marker The marker to check.
1428 * @param {google.maps.LatLngBounds} bounds The bounds to check against.
1429 * @return {boolean} True if the marker is in the bounds.
1430 */
1431MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) {
1432    return bounds.contains(marker.getPosition());
1433};
1434
1435
1436/**
1437 * Adds a marker to a cluster, or creates a new cluster.
1438 *
1439 * @param {google.maps.Marker} marker The marker to add.
1440 */
1441MarkerClusterer.prototype.addToClosestCluster_ = function(marker) {
1442    var i, d, cluster, center;
1443    var distance = 40000; // Some large number
1444    var clusterToAddTo = null;
1445    for (i = 0; i < this.clusters_.length; i++) {
1446        cluster = this.clusters_[i];
1447        center = cluster.getCenter();
1448        if (center) {
1449            d = this.distanceBetweenPoints_(center, marker.getPosition());
1450            if (d < distance) {
1451                distance = d;
1452                clusterToAddTo = cluster;
1453            }
1454        }
1455    }
1456
1457    if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
1458        clusterToAddTo.addMarker(marker);
1459    } else {
1460        cluster = new Cluster(this);
1461        cluster.addMarker(marker);
1462        this.clusters_.push(cluster);
1463    }
1464};
1465
1466
1467/**
1468 * Creates the clusters. This is done in batches to avoid timeout errors
1469 *  in some browsers when there is a huge number of markers.
1470 *
1471 * @param {number} iFirst The index of the first marker in the batch of
1472 *  markers to be added to clusters.
1473 */
1474MarkerClusterer.prototype.createClusters_ = function(iFirst) {
1475    var i, marker;
1476    var mapBounds;
1477    var cMarkerClusterer = this;
1478    if (!this.ready_) {
1479        return;
1480    }
1481
1482    // Cancel previous batch processing if we're working on the first batch:
1483    if (iFirst === 0) {
1484        /**
1485         * This event is fired when the <code>MarkerClusterer</code> begins
1486         *  clustering markers.
1487         * @name MarkerClusterer#clusteringbegin
1488         * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
1489         * @event
1490         */
1491        google.maps.event.trigger(this, "clusteringbegin", this);
1492
1493        if (typeof this.timerRefStatic !== "undefined") {
1494            clearTimeout(this.timerRefStatic);
1495            delete this.timerRefStatic;
1496        }
1497    }
1498
1499    // Get our current map view bounds.
1500    // Create a new bounds object so we don't affect the map.
1501    //
1502    // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug:
1503    if (this.getMap().getZoom() > 3) {
1504        mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(),
1505            this.getMap().getBounds().getNorthEast());
1506    } else {
1507        mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625));
1508    }
1509    var bounds = this.getExtendedBounds(mapBounds);
1510
1511    var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length);
1512
1513    for (i = iFirst; i < iLast; i++) {
1514        marker = this.markers_[i];
1515        if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
1516            if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) {
1517                this.addToClosestCluster_(marker);
1518            }
1519        }
1520    }
1521
1522    if (iLast < this.markers_.length) {
1523        this.timerRefStatic = setTimeout(function() {
1524            cMarkerClusterer.createClusters_(iLast);
1525        }, 0);
1526    } else {
1527        delete this.timerRefStatic;
1528
1529        /**
1530         * This event is fired when the <code>MarkerClusterer</code> stops
1531         *  clustering markers.
1532         * @name MarkerClusterer#clusteringend
1533         * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
1534         * @event
1535         */
1536        google.maps.event.trigger(this, "clusteringend", this);
1537    }
1538};
1539
1540
1541/**
1542 * Extends an object's prototype by another's.
1543 *
1544 * @param {Object} obj1 The object to be extended.
1545 * @param {Object} obj2 The object to extend with.
1546 * @return {Object} The new extended object.
1547 * @ignore
1548 */
1549MarkerClusterer.prototype.extend = function(obj1, obj2) {
1550    return (function(object) {
1551        var property;
1552        for (property in object.prototype) {
1553            this.prototype[property] = object.prototype[property];
1554        }
1555        return this;
1556    }).apply(obj1, [obj2]);
1557};
1558
1559
1560/**
1561 * The default function for determining the label text and style
1562 * for a cluster icon.
1563 *
1564 * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster.
1565 * @param {number} numStyles The number of marker styles available.
1566 * @return {ClusterIconInfo} The information resource for the cluster.
1567 * @constant
1568 * @ignore
1569 */
1570MarkerClusterer.CALCULATOR = function(markers, numStyles) {
1571    var index = 0;
1572    var title = "";
1573    var count = markers.length.toString();
1574
1575    var dv = count;
1576    while (dv !== 0) {
1577        dv = parseInt(dv / 10, 10);
1578        index++;
1579    }
1580
1581    index = Math.min(index, numStyles);
1582    return {
1583        text: count,
1584        index: index,
1585        title: title
1586    };
1587};
1588
1589
1590/**
1591 * The number of markers to process in one batch.
1592 *
1593 * @type {number}
1594 * @constant
1595 */
1596MarkerClusterer.BATCH_SIZE = 2000;
1597
1598
1599/**
1600 * The number of markers to process in one batch (IE only).
1601 *
1602 * @type {number}
1603 * @constant
1604 */
1605MarkerClusterer.BATCH_SIZE_IE = 500;
1606
1607
1608/**
1609 * The default root name for the marker cluster images.
1610 *
1611 * @type {string}
1612 * @constant
1613 */
1614MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m";
1615
1616
1617/**
1618 * The default extension name for the marker cluster images.
1619 *
1620 * @type {string}
1621 * @constant
1622 */
1623MarkerClusterer.IMAGE_EXTENSION = "png";
1624
1625
1626/**
1627 * The default array of sizes for the marker cluster images.
1628 *
1629 * @type {Array.<number>}
1630 * @constant
1631 */
1632MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90];

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.