PageSourceSearch

https://www.impresaschiabel.it/wp-content/plugins/realia/libraries…/markerclusterer.js?ver=6.1.14

js impresaschiabel.it collected 2026-10-04 06:00:18 UTC 50,626 bytes, 1,653 lines download raw bytes

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