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