PageSourceSearch

https://americanproperties.net/wp-content/themes/wpresidence/js/google_js/markerclusterer.js?ver=1.0

js americanproperties.net collected 2026-09-29 11:36:56 UTC 51,685 bytes, 1,644 lines download raw bytes

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

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.