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