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