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