PageSourceSearch

https://phyris.club/served_assets/@google/markerclusterer/src/markerclusterer.js

js phyris.club collected 2026-09-25 22:59:23 UTC 33,823 bytes, 1,322 lines download raw bytes

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

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.