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