1// ==ClosureCompiler== 2// @compilation_level ADVANCED_OPTIMIZATIONS 3// @externs_url https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/maps/google_maps_api_v3.js 4// ==/ClosureCompiler== 5 6/** 7 * @name Gmaps MarkerClusterer for Google Maps v3 8 * @version version 1.2.0 9 * @author Luke Mahe 10 * @author Sebastian Hösl <[email protected]> 11 * @fileoverview 12 * The library creates and manages per-zoom-level clusters for large amounts of 13 * markers. 14 * This is a v3 implementation of the v2 MarkerClusterer 15 * {@link http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/} 16 * ></a>. 17 */ 18 19/** 20 * @license 21 * 22 * Licensed under the Apache License, Version 2.0 (the "License"); 23 * you may not use this file except in compliance with the License. 24 * You may obtain a copy of the License at 25 * 26 * http://www.apache.org/licenses/LICENSE-2.0 27 * 28 * Unless required by applicable law or agreed to in writing, software 29 * distributed under the License is distributed on an "AS IS" BASIS, 30 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 * See the License for the specific language governing permissions and 32 * limitations under the License. 33 */ 34 35/** 36 * A Marker Clusterer that clusters markers. 37 * 38 * @param {google.maps.Map} map The Google map to attach to. 39 * @param {Array.<google.maps.Marker>=} opt_markers Optional markers to add to 40 * the cluster. 41 * @param {Object=} opt_options support the following options: 42 * 'gridSize': (number) The grid size of a cluster in pixels. 43 * 'maxZoom': (number) The maximum zoom level that a marker can be part of a 44 * cluster. 45 * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a 46 * cluster is to zoom into it. 47 * 'averageCenter': (boolean) Whether the center of each cluster should be 48 * the average of all markers in the cluster. 49 * 'minimumClusterSize': (number) The minimum number of markers to be in a 50 * cluster before the markers are hidden and a count 51 * is shown. 52 * 'ignoreHiddenMarkers': (boolean) Whether to ignore markers that are not 53 * visible or count and cluster them anyway 54 * 'styles': (Array) An array of objects with these properties: 55 * 'url': (string) The image url. 56 * 'height': (number) The image height. 57 * 'width': (number) The image width. 58 * 'anchor': (Array) The anchor position of the label text. 59 * 'textColor': (string) The text color. 60 * 'textSize': (number) The text size. 61 * 'backgroundPosition': (string) The position of the backgound x, y. 62 * 'iconAnchor': (Array) The anchor position of the icon x, y. 63 * 'cssClass': (string) One or more CSS class for styling this marker. 64 * 'onMouseoverCluster': (function) The event handler used for onmouseover 65 * each cluster 66 * 'onMouseoutCluster': (function) The event handler used for onmouseout 67 * each cluster 68 * 'drawCluster': (function) Custom draw method for ClusterIcon 69 * 'hideCluster': (function) Custom hide method for ClusterIcon 70 * 'showCluster': (function) Custom hide method for ClusterIcon 71 * 'onAddCluster': (function) Custom onAdd method for ClusterIcon 72 * 'onRemoveCluster': (function) Custom onRemove method for ClusterIcon 73 * @constructor 74 * @extends google.maps.OverlayView 75 */ 76function MarkerClusterer(map, opt_markers, opt_options) { 77 // MarkerClusterer implements google.maps.OverlayView interface. We use the 78 // extend function to extend MarkerClusterer with google.maps.OverlayView 79 // because it might not always be available when the code is defined so we 80 // look for it at the last possible moment. If it doesn't exist now then 81 // there is no point going ahead :) 82 this.extend(MarkerClusterer, google.maps.OverlayView); 83 this.map_ = map; 84 85 /** 86 * @type {Array.<google.maps.Marker>} 87 * @private 88 */ 89 this.markers_ = []; 90 91 /** 92 * @type {Array.<Cluster>} 93 */
94 this.clusters_ = []; 95 96 /** 97 * @type {Object} holding information about every markers cluster 98 */ 99 this.markersCluster_ = {}; 100 101 /** 102 * @type {Number} Unique markers ID 103 */ 104 this.markersUniqueID = 1; 105 106 this.sizes = [53, 56, 66, 78, 90]; 107 108 /** 109 * @private 110 */ 111 this.styles_ = []; 112 113 /** 114 * @private 115 */ 116 this.cssClass_ = ''; 117 118 /** 119 * @private 120 * @type {string} Set a default Cluster Class 121 */ 122 this.cssDefaultClass_ = 'cluster'; 123 124 /** 125 * @private 126 */ 127 this.setIndex_ = 0; 128 129 /** 130 * @type {boolean} 131 * @private 132 */ 133 this.ready_ = false; 134 135 var options = opt_options || {}; 136 137 /** 138 * @type {number} 139 * @private 140 */ 141 this.gridSize_ = (options['gridSize'] !== undefined) ? options['gridSize'] : 60; 142 143 /** 144 * @private 145 */ 146 this.minClusterSize_ = options['minimumClusterSize'] || 2; 147 148 /** 149 * @type {boolean} 150 * @private 151 */ 152 this.ignoreHiddenMarkers_ = options['ignoreHiddenMarkers'] || false; 153 154 /** 155 * @type {?number} 156 * @private 157 */ 158 this.maxZoom_ = options['maxZoom'] || null; 159 160 this.styles_ = options['styles'] || []; 161 162 this.cssClass_ = options['cssClass'] || null; 163 164 /** 165 * @type {string} 166 * @private 167 */ 168 this.imagePath_ = options['imagePath'] || 169 this.MARKER_CLUSTER_IMAGE_PATH_; 170 171 /** 172 * @type {string} 173 * @private 174 */ 175 this.imageExtension_ = options['imageExtension'] || 176 this.MARKER_CLUSTER_IMAGE_EXTENSION_; 177 178 /** 179 * @type {boolean} 180 * @private 181 */ 182 this.zoomOnClick_ = true; 183 184 if (options['zoomOnClick'] != undefined) { 185 this.zoomOnClick_ = options['zoomOnClick']; 186 } 187 188 /** 189 * @type {boolean} 190 * @private 191 */ 192 this.averageCenter_ = false; 193 194 if (options['averageCenter'] != undefined) { 195 this.averageCenter_ = options['averageCenter']; 196 } 197 198 /** 199 * @type {function} 200 * @private 201 */ 202 this.onMouseoverCluster_ = options['onMouseoverCluster']; 203 204 /** 205 * @type {function} 206 * @private 207 */ 208 this.onMouseoutCluster_ = options['onMouseoutCluster']; 209 210 /** 211 * @type {function} 212 * @private 213 */ 214 this.drawCluster_ = options['drawCluster']; 215 216 /** 217 * @type {function} 218 * @private 219 */ 220 this.hideCluster_ = options['hideCluster']; 221 222 /** 223 * @type {function} 224 * @private 225 */ 226 this.showCluster_ = options['showCluster']; 227 228 /** 229 * @type {function} 230 * @private 231 */ 232 this.onAddCluster_ = options['onAddCluster']; 233 234 /** 235 * @type {function} 236 * @private 237 */ 238 this.onRemoveCluster_ = options['onRemoveCluster']; 239 240 this.setupStyles_(); 241 242 this.setMap(map); 243 244 /** 245 * @type {number} 246 * @private 247 */ 248 this.prevZoom_ = this.map_.getZoom(); 249 250 // Add the map event listeners 251 var that = this; 252 google.maps.event.addListener(this.map_, 'zoom_changed', function() { 253 var zoom = that.map_.getZoom(); 254 255 if (that.prevZoom_ != zoom) { 256 that.prevZoom_ = zoom; 257 that.resetViewport(); 258 } 259 }); 260 261 google.maps.event.addListener(this.map_, 'idle', function() { 262 that.redraw(); 263 }); 264 265 // Finally, add the markers 266 if (opt_markers && opt_markers.length) { 267 this.addMarkers(opt_markers, false); 268 } 269} 270 271 272/** 273 * The marker cluster image path. 274 * 275 * @type {string} 276 * @private 277 */ 278MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = 'https://raw.githubusercontent.com/gmaps-marker-clusterer/gmaps-marker-clusterer/master/images/m'; 279 280 281/** 282 * The marker cluster image path. 283 * 284 * @type {string} 285 * @private 286 */ 287MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; 288 289 290/** 291 * Extends a objects prototype by anothers. 292 * 293 * @param {Object} obj1 The object to be extended. 294 * @param {Object} obj2 The object to extend with. 295 * @return {Object} The new extended object. 296 * @ignore 297 */ 298MarkerClusterer.prototype.extend = function(obj1, obj2) { 299 return (function(object) { 300 for (var property in object.prototype) { 301 this.prototype[property] = object.prototype[property]; 302 } 303 return this; 304 }).apply(obj1, [obj2]); 305}; 306 307 308/** 309 * Implementaion of the interface method. 310 * @ignore 311 */ 312MarkerClusterer.prototype.onAdd = function() { 313 this.setReady_(true); 314}; 315 316/** 317 * Implementaion of the interface method. 318 * @ignore 319 */ 320MarkerClusterer.prototype.draw = function() {}; 321 322/** 323 * Sets up the styles object. 324 * 325 * @private 326 */ 327MarkerClusterer.prototype.setupStyles_ = function() { 328 if (this.styles_.length) { 329 return; 330 } 331 332 for (var i = 0, size; size = this.sizes[i]; i++) { 333 var url = ''; 334 if (typeof this.imagePath_ === 'function') { 335 url = this.imagePath_(i, size); 336 } else { 337 url = this.imagePath_ + (i + 1) + '.' + this.imageExtension_; 338 } 339 this.styles_.push({ 340 url: url, 341 height: size, 342 width: size 343 }); 344 } 345}; 346 347/** 348 * Fit the map to the bounds of the markers in the clusterer. 349 */ 350MarkerClusterer.prototype.fitMapToMarkers = function() { 351 var markers = this.getMarkers(); 352 var bounds = new google.maps.LatLngBounds(); 353 for (var i = 0, marker; marker = markers[i]; i++) { 354 bounds.extend(marker.getPosition()); 355 } 356 357 this.map_.fitBounds(bounds); 358}; 359 360 361/** 362 * Sets the styles. 363 * 364 * @param {Object} styles The style to set. 365 */ 366MarkerClusterer.prototype.setStyles = function(styles) { 367 this.styles_ = styles; 368}; 369 370 371/** 372 * Gets the styles. 373 * 374 * @return {Object} The styles object. 375 */ 376MarkerClusterer.prototype.getStyles = function() { 377 return this.styles_; 378}; 379 380 381/** 382 * Whether zoom on click is set. 383 * 384 * @return {boolean} True if zoomOnClick_ is set. 385 */ 386MarkerClusterer.prototype.isZoomOnClick = function() { 387 return this.zoomOnClick_; 388}; 389 390/** 391 * Whether average center is set. 392 * 393 * @return {boolean} True if averageCenter_ is set. 394 */ 395MarkerClusterer.prototype.isAverageCenter = function() { 396 return this.averageCenter_; 397}; 398 399 400/** 401 * Returns the array of markers in the clusterer. 402 * 403 * @return {Array.<google.maps.Marker>} The markers. 404 */ 405MarkerClusterer.prototype.getMarkers = function() { 406 return this.markers_; 407}; 408 409 410/** 411 * Returns the number of markers in the clusterer 412 * 413 * @return {Number} The number of markers. 414 */ 415MarkerClusterer.prototype.getTotalMarkers = function() { 416 return this.markers_.length; 417}; 418 419 420/** 421 * Sets the max zoom for the clusterer. 422 * 423 * @param {number} maxZoom The max zoom level. 424 */ 425MarkerClusterer.prototype.setMaxZoom = function(maxZoom) { 426 this.maxZoom_ = maxZoom; 427}; 428 429 430/** 431 * Gets the max zoom for the clusterer. 432 * 433 * @return {number} The max zoom level. 434 */ 435MarkerClusterer.prototype.getMaxZoom = function() { 436 return this.maxZoom_; 437}; 438 439/** 440 * Gets marker's cluster object based on given marker 441 * 442 * @param {google.maps.Marker} marker 443 * 444 * @return {Cluster} 445 */ 446MarkerClusterer.prototype.getMarkersCluster = function(marker) {
447 return this.clusters_[this.markersCluster_[marker.uniqueID]]; 448}; 449 450/** 451 * The function for calculating the cluster icon image. 452 * 453 * @param {Array.<google.maps.Marker>} markers The markers in the clusterer. 454 * @param {number} numStyles The number of styles available. 455 * @return {Object} A object properties: 'text' (string) and 'index' (number). 456 * @private 457 */ 458MarkerClusterer.prototype.calculator_ = function(markers, numStyles) { 459 var index = 0; 460 var count = markers.length; 461 var dv = count; 462 while (dv !== 0) { 463 dv = parseInt(dv / 10, 10); 464 index++; 465 } 466 467 index = Math.min(index, numStyles); 468 469 return { 470 text: count, 471 index: index 472 }; 473}; 474 475 476/** 477 * Set the calculator function. 478 * 479 * @param {function(Array, number)} calculator The function to set as the 480 * calculator. The function should return a object properties: 481 * 'text' (string) and 'index' (number). 482 * 483 */ 484MarkerClusterer.prototype.setCalculator = function(calculator) { 485 this.calculator_ = calculator; 486}; 487 488 489/** 490 * Get the calculator function. 491 * 492 * @return {function(Array, number)} the calculator function. 493 */ 494MarkerClusterer.prototype.getCalculator = function() { 495 return this.calculator_; 496}; 497 498 499/** 500 * Add an array of markers to the clusterer. 501 * 502 * @param {Array.<google.maps.Marker>} markers The markers to add. 503 * @param {boolean=} opt_nodraw Whether to redraw the clusters. 504 */ 505MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) { 506 for (var i = 0, marker; marker = markers[i]; i++) { 507 this.pushMarkerTo_(marker); 508 } 509 if (!opt_nodraw) { 510 this.redraw(); 511 } 512}; 513 514 515/** 516 * Pushes a marker to the clusterer. 517 * 518 * @param {google.maps.Marker} marker The marker to add. 519 * @private 520 */ 521MarkerClusterer.prototype.pushMarkerTo_ = function(marker) { 522 marker.isAdded = false; 523 if (marker['draggable']) { 524 // If the marker is draggable add a listener so we update the clusters on 525 // the drag end. 526 var that = this; 527 google.maps.event.addListener(marker, 'dragend', function() { 528 marker.isAdded = false; 529 that.repaint(); 530 }); 531 } 532 marker.uniqueID = this.markersUniqueID; 533 this.markersUniqueID++; 534 this.markers_.push(marker); 535}; 536 537 538/** 539 * Adds a marker to the clusterer and redraws if needed. 540 * 541 * @param {google.maps.Marker} marker The marker to add. 542 * @param {boolean=} opt_nodraw Whether to redraw the clusters. 543 */ 544MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) { 545 this.pushMarkerTo_(marker); 546 if (!opt_nodraw) { 547 this.redraw(); 548 } 549}; 550 551 552/** 553 * Removes a marker and returns true if removed, false if not 554 * 555 * @param {google.maps.Marker} marker The marker to remove 556 * @return {boolean} Whether the marker was removed or not 557 * @private 558 */ 559MarkerClusterer.prototype.removeMarker_ = function(marker) { 560 var index = -1; 561 if (this.markers_.indexOf) { 562 index = this.markers_.indexOf(marker); 563 } else { 564 for (var i = 0, m; m = this.markers_[i]; i++) { 565 if (m == marker) { 566 index = i; 567 break; 568 } 569 } 570 } 571 572 if (index == -1) { 573 // Marker is not in our list of markers. 574 return false; 575 } 576 577 marker.setMap(null); 578 579 this.markers_.splice(index, 1); 580 delete this.markersCluster_[marker.uniqueID]; 581 582 return true; 583}; 584 585 586/** 587 * Remove a marker from the cluster. 588 * 589 * @param {google.maps.Marker} marker The marker to remove. 590 * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 591 * @return {boolean} True if the marker was removed. 592 */ 593MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) { 594 var removed = this.removeMarker_(marker); 595 596 if (!opt_nodraw && removed) { 597 this.resetViewport(); 598 this.redraw(); 599 return true; 600 } else { 601 return false; 602 } 603}; 604 605 606/** 607 * Removes an array of markers from the cluster. 608 * 609 * @param {Array.<google.maps.Marker>} markers The markers to remove. 610 * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 611 */ 612MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) { 613 var removed = false;
614 615 for (var i = markers.length; i >= 0; i--) { 616 var marker = markers[i]; 617 var r = this.removeMarker_(marker); 618 removed = removed || r; 619 } 620 621 if (!opt_nodraw && removed) { 622 this.resetViewport(); 623 this.redraw(); 624 return true; 625 } 626}; 627 628 629/** 630 * Sets the clusterer's ready state. 631 * 632 * @param {boolean} ready The state. 633 * @private 634 */ 635MarkerClusterer.prototype.setReady_ = function(ready) { 636 if (!this.ready_) { 637 this.ready_ = ready; 638 this.createClusters_(); 639 } 640}; 641 642 643/** 644 * Returns the number of clusters in the clusterer. 645 * 646 * @return {number} The number of clusters. 647 */ 648MarkerClusterer.prototype.getTotalClusters = function() { 649 return this.clusters_.length; 650}; 651 652 653/** 654 * Returns the list of clusters in the clusterer 655 * 656 * @return {Array.<Cluster>} The list of clusters. 657 */ 658MarkerClusterer.prototype.getClusters = function() { 659 return this.clusters_; 660}; 661 662 663/** 664 * Returns the google map that the clusterer is associated with. 665 * 666 * @return {google.maps.Map} The map. 667 */ 668MarkerClusterer.prototype.getMap = function() { 669 return this.map_; 670}; 671 672 673/** 674 * Sets the google map that the clusterer is associated with. 675 * 676 * @param {google.maps.Map} map The map. 677 */ 678MarkerClusterer.prototype.setMap = function(map) { 679 this.map_ = map; 680}; 681 682 683/** 684 * Returns the size of the grid. 685 * 686 * @return {number} The grid size. 687 */ 688MarkerClusterer.prototype.getGridSize = function() { 689 return this.gridSize_; 690}; 691 692 693/** 694 * Sets the size of the grid. 695 * 696 * @param {number} size The grid size. 697 */ 698MarkerClusterer.prototype.setGridSize = function(size) { 699 this.gridSize_ = size; 700}; 701 702 703/** 704 * Returns the min cluster size. 705 * 706 * @return {number} The grid size. 707 */ 708MarkerClusterer.prototype.getMinClusterSize = function() { 709 return this.minClusterSize_; 710}; 711 712/** 713 * Sets the min cluster size. 714 * 715 * @param {number} size The grid size. 716 */ 717MarkerClusterer.prototype.setMinClusterSize = function(size) { 718 this.minClusterSize_ = size; 719}; 720 721 722/** 723 * Extends a bounds object by the grid size. 724 * 725 * @param {google.maps.LatLngBounds} bounds The bounds to extend. 726 * @return {google.maps.LatLngBounds}
726 The extended bounds. 727 */ 728MarkerClusterer.prototype.getExtendedBounds = function(bounds) { 729 var projection = this.getProjection(); 730 731 // Turn the bounds into latlng. 732 var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), 733 bounds.getNorthEast().lng()); 734 var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), 735 bounds.getSouthWest().lng()); 736 737 // Convert the points to pixels and the extend out by the grid size. 738 var trPix = projection.fromLatLngToDivPixel(tr); 739 trPix.x += this.gridSize_; 740 trPix.y -= this.gridSize_; 741 742 var blPix = projection.fromLatLngToDivPixel(bl); 743 blPix.x -= this.gridSize_; 744 blPix.y += this.gridSize_; 745 746 // Convert the pixel points back to LatLng 747 var ne = projection.fromDivPixelToLatLng(trPix); 748 var sw = projection.fromDivPixelToLatLng(blPix); 749 750 // Extend the bounds to contain the new bounds. 751 bounds.extend(ne); 752 bounds.extend(sw); 753 754 return bounds; 755}; 756 757 758/** 759 * Determins if a marker is contained in a bounds. 760 * 761 * @param {google.maps.Marker} marker The marker to check. 762 * @param {google.maps.LatLngBounds} bounds The bounds to check against. 763 * @return {boolean} True if the marker is in the bounds. 764 * @private 765 */ 766MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) { 767 return bounds.contains(marker.getPosition()); 768}; 769 770 771/** 772 * Clears all clusters and markers from the clusterer. 773 */ 774MarkerClusterer.prototype.clearMarkers = function() { 775 this.resetViewport(true); 776 777 // Set the markers a empty array. 778 this.markers_ = []; 779 this.markersCluster_ = {}; 780 this.markersUniqueID = 1; 781}; 782 783 784/** 785 * Clears all existing clusters and recreates them. 786 * @param {boolean} opt_hide To also hide the marker. 787 */ 788MarkerClusterer.prototype.resetViewport = function(opt_hide) { 789 // Remove all the clusters 790 for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 791 cluster.remove(); 792 } 793 794 // Reset the markers to not be added and to be invisible. 795 for (var i = 0, marker; marker = this.markers_[i]; i++) { 796 marker.isAdded = false; 797 if (opt_hide) { 798 marker.setMap(null); 799 } 800 } 801 802 this.clusters_ = []; 803 this.markersCluster_ = {}; 804 this.markersUniqueID = 1; 805}; 806 807/** 808 * 809 */ 810MarkerClusterer.prototype.repaint = function() { 811 var oldClusters = this.clusters_.slice(); 812 this.clusters_.length = 0; 813 this.resetViewport(); 814 this.redraw(); 815 816 // Remove the old clusters. 817 // Do it in a timeout so the other clusters have been drawn first. 818 window.setTimeout(function() { 819 for (var i = 0, cluster; cluster = oldClusters[i]; i++) { 820 cluster.remove(); 821 } 822 }, 0); 823}; 824 825 826/** 827 * Redraws the clusters. 828 */ 829MarkerClusterer.prototype.redraw = function() { 830 this.createClusters_(); 831}; 832 833 834/** 835 * Calculates the distance between two latlng locations in km. 836 * @see http://www.movable-type.co.uk/scripts/latlong.html 837 * 838 * @param {google.maps.LatLng} p1 The first lat lng point. 839 * @param {google.maps.LatLng} p2 The second lat lng point. 840 * @return {number} The distance between the two points in km. 841 * @private 842 */ 843MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { 844 if (!p1 || !p2) { 845 return 0; 846 } 847 848 var R = 6371; // Radius of the Earth in km 849 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; 850 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; 851 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 852 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * 853 Math.sin(dLon / 2) * Math.sin(dLon / 2); 854 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 855 var d = R * c; 856 return d; 857}; 858 859 860/** 861 * Add a marker to a cluster, or creates a new cluster. 862 * 863 * @param {google.maps.Marker} marker The marker to add. 864 * @private 865 */ 866MarkerClusterer.prototype.addToClosestCluster_ = function(marker) { 867 var distance = 40000; // Some large number 868 var clusterToAddTo = null; 869 var pos = marker.getPosition(); 870 var clusterIndex = null; 871 for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 872 var center = cluster.getCenter(); 873 if (center) { 874 var d = this.distanceBetweenPoints_(center, marker.getPosition()); 875 if (d < distance) { 876 distance = d; 877 clusterToAddTo = cluster; 878 clusterIndex = i; 879 } 880 } 881 } 882 883 if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { 884 clusterToAddTo.addMarker(marker); 885 } else { 886 var cluster = new Cluster(this); 887 cluster.addMarker(marker);
888 this.clusters_.push(cluster); 889 clusterIndex = this.clusters_.length - 1; 890 } 891 892 if (marker.isAdded) { 893 this.markersCluster_[marker.uniqueID] = clusterIndex; 894 } 895}; 896 897 898/** 899 * Creates the clusters. 900 * 901 * @private 902 */ 903MarkerClusterer.prototype.createClusters_ = function() { 904 if (!this.ready_) { 905 return; 906 } 907 908 // Get our current map view bounds. 909 // Create a new bounds object so we don't affect the map. 910 var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), 911 this.map_.getBounds().getNorthEast()); 912 var bounds = this.getExtendedBounds(mapBounds); 913 914 for (var i = 0, marker; marker = this.markers_[i]; i++) { 915 if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds) && (!this.ignoreHiddenMarkers_ || marker.getVisible())) { 916 this.addToClosestCluster_(marker); 917 } 918 } 919}; 920 921 922/** 923 * A cluster that contains markers. 924 * 925 * @param {MarkerClusterer} markerClusterer The markerclusterer that this 926 * cluster is associated with. 927 * @constructor 928 * @ignore 929 */ 930function Cluster(markerClusterer) { 931 this.markerClusterer_ = markerClusterer; 932 this.map_ = markerClusterer.getMap(); 933 this.gridSize_ = markerClusterer.getGridSize(); 934 this.minClusterSize_ = markerClusterer.getMinClusterSize(); 935 this.averageCenter_ = markerClusterer.isAverageCenter(); 936 this.center_ = null; 937 this.markers_ = []; 938 this.bounds_ = null; 939 this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(), 940 markerClusterer.getGridSize()); 941} 942 943/** 944 * Determins if a marker is already added to the cluster. 945 * 946 * @param {google.maps.Marker} marker The marker to check. 947 * @return {boolean} True if the marker is already added. 948 */ 949Cluster.prototype.isMarkerAlreadyAdded = function(marker) { 950 if (this.markers_.indexOf) { 951 return this.markers_.indexOf(marker) != -1; 952 } else { 953 for (var i = 0, m; m = this.markers_[i]; i++) { 954 if (m == marker) { 955 return true; 956 } 957 } 958 } 959 return false; 960}; 961 962 963/** 964 * Add a marker the cluster. 965 * 966 * @param {google.maps.Marker} marker The marker to add. 967 * @return {boolean} True if the marker was added. 968 */ 969Cluster.prototype.addMarker = function(marker) { 970 if (this.isMarkerAlreadyAdded(marker)) { 971 return false; 972 } 973 974 if (!this.center_) { 975 this.center_ = marker.getPosition(); 976 this.calculateBounds_(); 977 } else { 978 if (this.averageCenter_) { 979 var l = this.markers_.length + 1; 980 var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l; 981 var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l; 982 this.center_ = new google.maps.LatLng(lat, lng); 983 this.calculateBounds_(); 984 } 985 } 986 987 marker.isAdded = true; 988 this.markers_.push(marker); 989 990 var len = this.markers_.length; 991 if (len < this.minClusterSize_ && marker.getMap() != this.map_) { 992 // Min cluster size not reached so show the marker. 993 marker.setMap(this.map_); 994 } 995 996 if (len == this.minClusterSize_) { 997 // Hide the markers that were showing. 998 for (var i = 0; i < len; i++) { 999 this.markers_[i].setMap(null); 1000 } 1001 } 1002 1003 if (len >= this.minClusterSize_) { 1004 marker.setMap(null); 1005 } 1006 1007 this.updateIcon(); 1008 return true; 1009}; 1010 1011 1012/** 1013 * Returns the marker clusterer that the cluster is associated with. 1014 * 1015 * @return {MarkerClusterer} The associated marker clusterer. 1016 */ 1017Cluster.prototype.getMarkerClusterer = function() { 1018 return this.markerClusterer_; 1019}; 1020 1021 1022/** 1023 * Returns the bounds of the cluster. 1024 * 1025 * @return {google.maps.LatLngBounds} the cluster bounds. 1026 */ 1027Cluster.prototype.getBounds = function() { 1028 var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 1029 var markers = this.getMarkers(); 1030 for (var i = 0, marker; marker = markers[i]; i++) { 1031 bounds.extend(marker.getPosition()); 1032 } 1033 return bounds; 1034}; 1035 1036 1037/** 1038 * Removes the cluster 1039 */ 1040Cluster.prototype.remove = function() { 1041 this.clusterIcon_.remove(); 1042 this.markers_.length = 0; 1043 delete this.markers_; 1044}; 1045 1046 1047/** 1048 * Returns the center of the cluster. 1049 * 1050 * @return {number} The cluster center. 1051 */ 1052Cluster.prototype.getSize = function() { 1053 return this.markers_.length; 1054}; 1055 1056 1057/** 1058 * Returns the center of the cluster. 1059 * 1060 * @return {Array.<google.maps.Marker>} The cluster center. 1061 */ 1062Cluster.prototype.getMarkers = function() { 1063 return this.markers_; 1064}; 1065 1066 1067/** 1068 * Returns the center of the cluster. 1069 * 1070 * @return {google.maps.LatLng} The cluster center. 1071 */ 1072Cluster.prototype.getCenter = function() { 1073 return this.center_; 1074}; 1075 1076 1077/**
1078 * Calculated the extended bounds of the cluster with the grid. 1079 * 1080 * @private 1081 */ 1082Cluster.prototype.calculateBounds_ = function() { 1083 var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 1084 this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); 1085}; 1086 1087 1088/** 1089 * Determines if a marker lies in the clusters bounds. 1090 * 1091 * @param {google.maps.Marker} marker The marker to check. 1092 * @return {boolean} True if the marker lies in the bounds. 1093 */ 1094Cluster.prototype.isMarkerInClusterBounds = function(marker) { 1095 return this.bounds_.contains(marker.getPosition()); 1096}; 1097 1098 1099/** 1100 * Returns the map that the cluster is associated with. 1101 * 1102 * @return {google.maps.Map} The map. 1103 */ 1104Cluster.prototype.getMap = function() { 1105 return this.map_; 1106}; 1107 1108 1109/** 1110 * Updates the cluster icon 1111 */ 1112Cluster.prototype.updateIcon = function() { 1113 var zoom = this.map_.getZoom(); 1114 var mz = this.markerClusterer_.getMaxZoom(); 1115 1116 if (mz && zoom > mz) { 1117 // The zoom is greater than our max zoom so show all the markers in cluster. 1118 for (var i = 0, marker; marker = this.markers_[i]; i++) { 1119 marker.setMap(this.map_); 1120 } 1121 return; 1122 } 1123 1124 if (this.markers_.length < this.minClusterSize_) { 1125 // Min cluster size not yet reached. 1126 this.clusterIcon_.hide(); 1127 return; 1128 } 1129 1130 var numStyles = this.markerClusterer_.getStyles().length; 1131 var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); 1132 this.clusterIcon_.setCenter(this.center_); 1133 this.clusterIcon_.setSums(sums); 1134 this.clusterIcon_.show(); 1135}; 1136 1137 1138/** 1139 * A cluster icon 1140 * 1141 * @param {Cluster} cluster The cluster to be associated with. 1142 * @param {Object} styles An object that has style properties: 1143 * 'url': (string) The image url. 1144 * 'height': (number) The image height. 1145 * 'width': (number) The image width. 1146 * 'anchor': (Array) The anchor position of the label text. 1147 * 'textColor': (string) The text color. 1148 * 'textSize': (number) The text size. 1149 * 'backgroundPosition: (string) The background postition x, y. 1150 * @param {number=} opt_padding Optional padding to apply to the cluster icon. 1151 * @constructor 1152 * @extends google.maps.OverlayView 1153 * @ignore 1154 */ 1155function ClusterIcon(cluster, styles, opt_padding) { 1156 cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); 1157 1158 this.styles_ = styles; 1159 this.padding_ = opt_padding || 0; 1160 this.cluster_ = cluster; 1161 this.center_ = null; 1162 this.map_ = cluster.getMap(); 1163 this.div_ = null; 1164 this.sums_ = null; 1165 this.visible_ = false; 1166 1167 this.setMap(this.map_); 1168} 1169 1170/** 1171 * Triggers the clusterclick event and zoom's if the option is set. 1172 * 1173 * @param {google.maps.MouseEvent} event The event to propagate 1174 */ 1175ClusterIcon.prototype.triggerClusterClick = function(event) { 1176 var markerClusterer = this.cluster_.getMarkerClusterer(); 1177 1178 // Trigger the clusterclick event. 1179 google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_, event); 1180 1181 if (markerClusterer.isZoomOnClick()) { 1182 // Zoom into the cluster. 1183 this.map_.fitBounds(this.cluster_.getBounds()); 1184 } 1185}; 1186 1187/** 1188 * Triggers the clustermouseover event. 1189 * @param {google.maps.MouseEvent} event The event to propagate 1190 */ 1191ClusterIcon.prototype.triggerClusterMouseover = function(event) { 1192 var markerClusterer = this.cluster_.getMarkerClusterer(); 1193 1194 // Trigger the clustermouseover event. 1195 google.maps.event.trigger(markerClusterer, 'clustermouseover', this.cluster_, event); 1196 1197 if (typeof this.cluster_.markerClusterer_.onMouseoverCluster_ === 'function') { 1198 this.cluster_.markerClusterer_.onMouseoverCluster_(this, event); 1199 } 1200}; 1201 1202/** 1203 * Triggers the clustermouseout event. 1204 * @param {google.maps.MouseEvent} event The event to propagate 1205 */ 1206ClusterIcon.prototype.triggerClusterMouseout = function(event) { 1207 var markerClusterer = this.cluster_.getMarkerClusterer(); 1208 1209 // Trigger the clustermouseout event. 1210 google.maps.event.trigger(markerClusterer, 'clustermouseout', this.cluster_, event); 1211 1212 if (typeof this.cluster_.markerClusterer_.onMouseoutCluster_ === 'function') { 1213 this.cluster_.markerClusterer_.onMouseoutCluster_(this, event); 1214 } 1215}; 1216 1217/** 1218 * Adding the cluster icon to the dom. 1219 * @ignore 1220 */ 1221ClusterIcon.prototype.onAdd = function () { 1222 if (typeof this.cluster_.markerClusterer_.onAddCluster_ === 'function') { 1223 this.cluster_.markerClusterer_.onAddCluster_(this); 1224 } 1225 else { 1226 defaultClusterOnAdd(this); 1227 } 1228}; 1229 1230/** 1231 * Default onAdd function 1232 * @ignore 1233 */ 1234function defaultClusterOnAdd(clusterIcon) { 1235 clusterIcon.div_ = document.createElement('DIV'); 1236 if (clusterIcon.visible_) { 1237 var pos = clusterIcon.getPosFromLatLng_(clusterIcon.center_); 1238 clusterIcon.div_.style.cssText = clusterIcon.createCss(pos); 1239 clusterIcon.div_.innerHTML = clusterIcon.sums_.text; 1240 clusterIcon.addClass(); 1241 } 1242 1243 var panes = clusterIcon.getPanes(); 1244 1245 panes.overlayMouseTarget.appendChild(clusterIcon.div_); 1246 1247 var isDragging = false;
1248 var isMouseDown = false; 1249 1250 google.maps.event.addDomListener(clusterIcon.div_, 'click', function(event) { 1251 // Only perform click when not preceded by a drag 1252 if (!isDragging) { 1253 clusterIcon.triggerClusterClick(event); 1254 } 1255 }); 1256 1257 google.maps.event.addDomListener(clusterIcon.div_, 'mousedown', function() { 1258 isDragging = false; 1259 isMouseDown = true; 1260 }); 1261 1262 google.maps.event.addDomListener(clusterIcon.div_, 'mouseup', function() { 1263 isDragging = false; 1264 isMouseDown = false; 1265 }); 1266 1267 google.maps.event.addDomListener(clusterIcon.div_, 'mousemove', function() { 1268 if (isMouseDown) { 1269 isDragging = true; 1270 } 1271 }); 1272 1273 google.maps.event.addDomListener(clusterIcon.div_, 'mouseover', function(event) { 1274 clusterIcon.triggerClusterMouseover(event); 1275 }); 1276 1277 google.maps.event.addDomListener(clusterIcon.div_, 'mouseout', function(event) { 1278 clusterIcon.triggerClusterMouseout(event); 1279 }); 1280} 1281 1282/** 1283 * Returns the position to place the div dending on the latlng. 1284 * 1285 * @param {google.maps.LatLng} latlng The position in latlng. 1286 * @return {google.maps.Point} The position in pixels. 1287 * @private 1288 */ 1289ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { 1290 var pos = this.getProjection().fromLatLngToDivPixel(latlng); 1291 1292 if (typeof this.iconAnchor_ === 'object' && this.iconAnchor_.length === 2) { 1293 pos.x -= this.iconAnchor_[0]; 1294 pos.y -= this.iconAnchor_[1]; 1295 } else { 1296 pos.x -= parseInt(this.width_ / 2, 10); 1297 pos.y -= parseInt(this.height_ / 2, 10); 1298 } 1299 return pos; 1300}; 1301 1302 1303/** 1304 * Draw the icon. 1305 * @ignore 1306 */ 1307ClusterIcon.prototype.draw = function () { 1308 if (typeof this.cluster_.markerClusterer_.drawCluster_ === 'function') { 1309 this.cluster_.markerClusterer_.drawCluster_(this); 1310 } 1311 else { 1312 defaultClusterDraw(this); 1313 } 1314}; 1315 1316/** 1317 * Default draw function 1318 * @ignore 1319 */ 1320function defaultClusterDraw(clusterIcon) { 1321 if (clusterIcon.visible_) { 1322 var pos = clusterIcon.getPosFromLatLng_(clusterIcon.center_); 1323 clusterIcon.div_.style.top = pos.y + 'px'; 1324 clusterIcon.div_.style.left = pos.x + 'px'; 1325 } 1326} 1327 1328 1329/** 1330 * Hide the icon. 1331 */ 1332ClusterIcon.prototype.hide = function() { 1333 if (typeof this.cluster_.markerClusterer_.hideCluster_ === 'function') { 1334 this.cluster_.markerClusterer_.hideCluster_(this); 1335 } 1336 else { 1337 defaultClusterHide(this); 1338 } 1339}; 1340 1341 1342/** 1343 * Default hide function 1344 * @ignore 1345 */ 1346function defaultClusterHide(clusterIcon) { 1347 if (clusterIcon.div_) { 1348 clusterIcon.div_.style.display = 'none'; 1349 clusterIcon.div_.className = ''; 1350 } 1351 clusterIcon.visible_ = false; 1352} 1353 1354 1355/** 1356 * Position and show the icon. 1357 */ 1358ClusterIcon.prototype.show = function() { 1359 if (typeof this.cluster_.markerClusterer_.showCluster_ === 'function') { 1360 this.cluster_.markerClusterer_.showCluster_(this); 1361 } 1362 else { 1363 defaultClusterShow(this); 1364 } 1365}; 1366 1367/** 1368 * Default show function 1369 * @ignore 1370 */ 1371function defaultClusterShow(clusterIcon) { 1372 if (clusterIcon.div_) { 1373 var pos = clusterIcon.getPosFromLatLng_(clusterIcon.center_); 1374 clusterIcon.div_.style.cssText = clusterIcon.createCss(pos); 1375 clusterIcon.div_.style.display = ''; 1376 clusterIcon.addClass(); 1377 } 1378 clusterIcon.visible_ = true; 1379} 1380 1381 1382/** 1383 * Remove the icon from the map 1384 */ 1385ClusterIcon.prototype.remove = function() { 1386 this.setMap(null); 1387}; 1388 1389 1390/** 1391 * Implementation of the onRemove interface. 1392 * @ignore 1393 */ 1394ClusterIcon.prototype.onRemove = function() { 1395 if (typeof this.cluster_.markerClusterer_.onRemoveCluster_ === 'function') { 1396 this.cluster_.markerClusterer_.onRemoveCluster_(this); 1397 } 1398 else { 1399 defaultClusterOnRemove(this); 1400 } 1401}; 1402 1403/** 1404 * Default onRemove function 1405 * @ignore 1406 */ 1407function defaultClusterOnRemove(clusterIcon) { 1408 if (clusterIcon.div_ && clusterIcon.div_.parentNode) { 1409 clusterIcon.hide(); 1410 clusterIcon.div_.parentNode.removeChild(clusterIcon.div_); 1411 clusterIcon.div_ = null; 1412 } 1413} 1414 1415 1416/** 1417 * Set the sums of the icon. 1418 * 1419 * @param {Object} sums The sums containing: 1420 * 'text': (string) The text to display in the icon. 1421 * 'index': (number) The style index of the icon. 1422 */ 1423ClusterIcon.prototype.setSums = function(sums) { 1424 this.sums_ = sums; 1425 this.text_ = sums.text; 1426 this.index_ = sums.index; 1427 if (this.div_) { 1428 this.div_.innerHTML = sums.text; 1429 } 1430 1431 this.useStyle(); 1432}; 1433 1434 1435/** 1436 * Sets the icon to the the styles. 1437 */ 1438ClusterIcon.prototype.useStyle = function() { 1439 var index = Math.max(0, this.sums_.index - 1); 1440 index = Math.min(this.styles_.length - 1, index); 1441 var style = this.styles_[index]; 1442 this.url_ = style['url']; 1443 this.height_ = style['height']; 1444 this.width_ = style['width']; 1445 this.textColor_ = style['textColor']; 1446 this.anchor_ = style['anchor']; 1447 this.textSize_ = style['textSize']; 1448 this.backgroundPosition_ = style['backgroundPosition']; 1449 this.iconAnchor_ = style['iconAnchor']; 1450 this.setIndex_ = index; 1451}; 1452 1453 1454/** 1455 * Sets the center of the icon. 1456 * 1457 * @param {google.maps.LatLng} center The latlng to set as the center. 1458 */ 1459ClusterIcon.prototype.setCenter = function(center) { 1460 this.center_ = center; 1461}; 1462 1463 1464/** 1465 * Create the css text based on the position of the icon. 1466 * 1467 * @param {google.maps.Point} pos The position. 1468 * @return {string} The css style text. 1469 */ 1470ClusterIcon.prototype.createCss = function(pos) { 1471 var style = []; 1472 var markerClusterer = this.cluster_.getMarkerClusterer(); 1473 1474 if (!markerClusterer.cssClass_) { 1475 style.push('background-image:url(' + this.url_ + ');'); 1476 var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0'; 1477 style.push('background-position:' + backgroundPosition + ';'); 1478 1479 if (typeof this.anchor_ === 'object') { 1480 if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && 1481 this.anchor_[0] < this.height_) { 1482 style.push('height:' + (this.height_ - this.anchor_[0]) + 1483 'px; padding-top:' + this.anchor_[0] + 'px;'); 1484 } else if (typeof this.anchor_[0] === 'number' && this.anchor_[0] < 0 && 1485 -this.anchor_[0] < this.height_) { 1486 style.push('height:' + this.height_ + 'px; line-height:' + (this.height_ + this.anchor_[0]) + 1487 'px;'); 1488 } else { 1489 style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + 1490 'px;'); 1491 } 1492 if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && 1493 this.anchor_[1] < this.width_) { 1494 style.push('width:' + (this.width_ - this.anchor_[1]) + 1495 'px; padding-left:' + this.anchor_[1] + 'px;'); 1496 } else { 1497 style.push('width:' + this.width_ + 'px; text-align:center;'); 1498 } 1499 } else { 1500 style.push('height:' + this.height_ + 'px; line-height:' + 1501 this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); 1502 } 1503 1504 var txtColor = this.textColor_ ? this.textColor_ : 'black'; 1505 var txtSize = this.textSize_ ? this.textSize_ : 11; 1506 1507 style.push('cursor:pointer; top:' + pos.y + 'px; left:' + 1508 pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + 1509 txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold'); 1510 1511 } else { 1512 style.push('top:' + pos.y + 'px; left:' + pos.x + 'px;'); 1513 } 1514 1515 return style.join(''); 1516}; 1517 1518/** 1519 * Set the class for the cluster icon 1520 * @ignore 1521 */ 1522ClusterIcon.prototype.addClass = function() { 1523 var markerClusterer = this.cluster_.getMarkerClusterer(); 1524 1525 if (markerClusterer.cssClass_) { 1526 this.div_.className = markerClusterer.cssClass_ + ' ' + markerClusterer.cssDefaultClass_ + this.setIndex_; 1527 } else { 1528 this.div_.className = markerClusterer.cssDefaultClass_ + this.setIndex_; 1529 } 1530} 1531 1532 1533// Export Symbols for Closure
1534// If you are not going to compile with closure then you can remove the 1535// code below. 1536window['MarkerClusterer'] = MarkerClusterer; 1537MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker; 1538MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers; 1539MarkerClusterer.prototype['clearMarkers'] = 1540 MarkerClusterer.prototype.clearMarkers; 1541MarkerClusterer.prototype['fitMapToMarkers'] = 1542 MarkerClusterer.prototype.fitMapToMarkers; 1543MarkerClusterer.prototype['getCalculator'] = 1544 MarkerClusterer.prototype.getCalculator; 1545MarkerClusterer.prototype['getGridSize'] = 1546 MarkerClusterer.prototype.getGridSize; 1547MarkerClusterer.prototype['getExtendedBounds'] = 1548 MarkerClusterer.prototype.getExtendedBounds; 1549MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap; 1550MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers; 1551MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom; 1552MarkerClusterer.prototype['getMarkersCluster'] = MarkerClusterer.prototype.getMarkersCluster; 1553MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles; 1554MarkerClusterer.prototype['getTotalClusters'] = 1555 MarkerClusterer.prototype.getTotalClusters; 1556MarkerClusterer.prototype['getTotalMarkers'] = 1557 MarkerClusterer.prototype.getTotalMarkers; 1558MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw; 1559MarkerClusterer.prototype['removeMarker'] = 1560 MarkerClusterer.prototype.removeMarker; 1561MarkerClusterer.prototype['removeMarkers'] = 1562 MarkerClusterer.prototype.removeMarkers; 1563MarkerClusterer.prototype['resetViewport'] = 1564 MarkerClusterer.prototype.resetViewport; 1565MarkerClusterer.prototype['repaint'] = 1566 MarkerClusterer.prototype.repaint; 1567MarkerClusterer.prototype['setCalculator'] = 1568 MarkerClusterer.prototype.setCalculator; 1569MarkerClusterer.prototype['setGridSize'] = 1570 MarkerClusterer.prototype.setGridSize; 1571MarkerClusterer.prototype['setMaxZoom'] = 1572 MarkerClusterer.prototype.setMaxZoom; 1573MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd; 1574MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw; 1575 1576Cluster.prototype['getCenter'] = Cluster.prototype.getCenter; 1577Cluster.prototype['getSize'] = Cluster.prototype.getSize; 1578Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers; 1579 1580ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd; 1581ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw; 1582ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove;
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.