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.