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