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