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