e_markerclusterer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1883 @@
1
+ /**
2
+ * @name Enhanced MarkerClusterer
3
+ * @version 0.0.0 [June 27, 2016]
4
+ * @author Sung Gon Yi (dervied from Gary Little and Hassan Mughal)
5
+ * @fileoverview
6
+ * The library creates and manages per-zoom-level clusters with various inforgraphics for large amounts of markers.
7
+ * <p>
8
+ * This is an enhanced version of MarkerClusterer based Google <a href="https://github.com/printercu/google-maps-utility-library-v3-read-only/tree/master/markerclustererplus">MarkerClustererPlus</a>. Clusters is represented by various infographics with some infomative animations. Base infographic chart is derived from <a href="https://github.com/hassanlatif/chart-marker-clusterer">Chart Marker Clusterer</a>.
9
+ */
10
+
11
+ /**
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+
25
+ /**
26
+ * @name ClusterIconStyle
27
+ * @class This class represents the object for values in the <code>styles</code> array passed
28
+ * to the {@link MarkerClusterer} constructor. The element in this array that is used to
29
+ * style the cluster icon is determined by calling the <code>calculator</code> function.
30
+ *
31
+ * @property {string} url The URL of the cluster icon image file. Required.
32
+ * @property {number} height The display height (in pixels) of the cluster icon. Required.
33
+ * @property {number} width The display width (in pixels) of the cluster icon. Required.
34
+ * @property {Array} [anchorText] The position (in pixels) from the center of the cluster icon to
35
+ * where the text label is to be centered and drawn. The format is <code>[yoffset, xoffset]</code>
36
+ * where <code>yoffset</code> increases as you go down from center and <code>xoffset</code>
37
+ * increases to the right of center. The default is <code>[0, 0]</code>.
38
+ * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the
39
+ * spot on the cluster icon that is to be aligned with the cluster position. The format is
40
+ * <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and
41
+ * <code>xoffset</code> increases to the right of the top-left corner of the icon. The default
42
+ * anchor position is the center of the cluster icon.
43
+ * @property {string} [textColor="black"] The color of the label text shown on the
44
+ * cluster icon.
45
+ * @property {number} [textSize=11] The size (in pixels) of the label text shown on the
46
+ * cluster icon.
47
+ * @property {string} [textDecoration="none"] The value of the CSS <code>text-decoration</code>
48
+ * property for the label text shown on the cluster icon.
49
+ * @property {string} [fontWeight="bold"] The value of the CSS <code>font-weight</code>
50
+ * property for the label text shown on the cluster icon.
51
+ * @property {string} [fontStyle="normal"] The value of the CSS <code>font-style</code>
52
+ * property for the label text shown on the cluster icon.
53
+ * @property {string} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code>
54
+ * property for the label text shown on the cluster icon.
55
+ * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image
56
+ * within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code>
57
+ * (the same format as for the CSS <code>background-position</code> property). You must set
58
+ * this property appropriately when the image defined by <code>url</code> represents a sprite
59
+ * containing multiple images. Note that the position <i>must</i> be specified in px units.
60
+ */
61
+ /**
62
+ * @name ClusterIconInfo
63
+ * @class This class is an object containing general information about a cluster icon. This is
64
+ * the object that a <code>calculator</code> function returns.
65
+ *
66
+ * @property {string} text The text of the label to be shown on the cluster icon.
67
+ * @property {number} index The index plus 1 of the element in the <code>styles</code>
68
+ * array to be used to style the cluster icon.
69
+ * @property {string} title The tooltip to display when the mouse moves over the cluster icon.
70
+ * If this value is <code>undefined</code> or <code>""</code>, <code>title</code> is set to the
71
+ * value of the <code>title</code> property passed to the MarkerClusterer.
72
+ */
73
+ /**
74
+ * A cluster icon.
75
+ *
76
+ * @constructor
77
+ * @extends google.maps.OverlayView
78
+ * @param {Cluster} cluster The cluster with which the icon is to be associated.
79
+ * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons
80
+ * to use for various cluster sizes.
81
+ * @private
82
+ */
83
+ function ClusterIcon(cluster, styles) {
84
+ cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
85
+
86
+ this.cluster_ = cluster;
87
+ this.className_ = cluster.getMarkerClusterer().getClusterClass();
88
+ this.styles_ = styles;
89
+ this.center_ = null;
90
+ this.div_ = null;
91
+ this.icon_div_ = null;
92
+ this.sums_ = null;
93
+ this.visible_ = false;
94
+
95
+ this.setMap(cluster.getMap()); // Note: this causes onAdd to be called
96
+ }
97
+
98
+ /**
99
+ * Adds the icon to the DOM.
100
+ */
101
+ ClusterIcon.prototype.onAdd = function () {
102
+ var cClusterIcon = this;
103
+ var cMouseDownInCluster;
104
+ var cDraggingMapByCluster;
105
+
106
+ this.div_ = document.createElement("div");
107
+ this.div_.className = this.className_;
108
+ this.icon_div_ = document.createElement("div");
109
+ this.icon_div_.className = this.className_;
110
+ if (this.visible_) {
111
+ this.show();
112
+ }
113
+
114
+ this.getPanes().overlayMouseTarget.appendChild(this.div_);
115
+ this.getPanes().overlayMouseTarget.appendChild(this.icon_div_);
116
+
117
+ // Fix for Issue 157
118
+ this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function () {
119
+ cDraggingMapByCluster = cMouseDownInCluster;
120
+ });
121
+
122
+ google.maps.event.addDomListener(this.div_, "mousedown", function () {
123
+ cMouseDownInCluster = true;
124
+ cDraggingMapByCluster = false;
125
+ });
126
+
127
+ google.maps.event.addDomListener(this.icon_div_, "mousedown", function () {
128
+ cMouseDownInCluster = true;
129
+ cDraggingMapByCluster = false;
130
+ });
131
+
132
+ google.maps.event.addDomListener(this.div_, "click", function (e) {
133
+ cMouseDownInCluster = false;
134
+ if (!cDraggingMapByCluster) {
135
+ var theBounds;
136
+ var mz;
137
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
138
+ /**
139
+ * This event is fired when a cluster marker is clicked.
140
+ * @name MarkerClusterer#click
141
+ * @param {Cluster} c The cluster that was clicked.
142
+ * @event
143
+ */
144
+ google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
145
+ google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
146
+
147
+ // The default click handler follows. Disable it by setting
148
+ // the zoomOnClick property to false.
149
+ if (mc.getZoomOnClick()) {
150
+ // Zoom into the cluster.
151
+ mz = mc.getMaxZoom();
152
+ theBounds = cClusterIcon.cluster_.getBounds();
153
+ mc.getMap().fitBounds(theBounds);
154
+ // There is a fix for Issue 170 here:
155
+ setTimeout(function () {
156
+ mc.getMap().fitBounds(theBounds);
157
+ // Don't zoom beyond the max zoom level
158
+ if (mz !== null && (mc.getMap().getZoom() > mz)) {
159
+ mc.getMap().setZoom(mz + 1);
160
+ }
161
+ }, 100);
162
+ }
163
+
164
+ // Prevent event propagation to the map:
165
+ e.cancelBubble = true;
166
+ if (e.stopPropagation) {
167
+ e.stopPropagation();
168
+ }
169
+ }
170
+ });
171
+
172
+ google.maps.event.addDomListener(this.icon_div_, "click", function (e) {
173
+ cMouseDownInCluster = false;
174
+ if (!cDraggingMapByCluster) {
175
+ var theBounds;
176
+ var mz;
177
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
178
+ /**
179
+ * This event is fired when a cluster marker is clicked.
180
+ * @name MarkerClusterer#click
181
+ * @param {Cluster} c The cluster that was clicked.
182
+ * @event
183
+ */
184
+ google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
185
+ google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
186
+
187
+ // The default click handler follows. Disable it by setting
188
+ // the zoomOnClick property to false.
189
+ if (mc.getZoomOnClick()) {
190
+ // Zoom into the cluster.
191
+ mz = mc.getMaxZoom();
192
+ theBounds = cClusterIcon.cluster_.getBounds();
193
+ mc.getMap().fitBounds(theBounds);
194
+ // There is a fix for Issue 170 here:
195
+ setTimeout(function () {
196
+ mc.getMap().fitBounds(theBounds);
197
+ // Don't zoom beyond the max zoom level
198
+ if (mz !== null && (mc.getMap().getZoom() > mz)) {
199
+ mc.getMap().setZoom(mz + 1);
200
+ }
201
+ }, 100);
202
+ }
203
+
204
+ // Prevent event propagation to the map:
205
+ e.cancelBubble = true;
206
+ if (e.stopPropagation) {
207
+ e.stopPropagation();
208
+ }
209
+ }
210
+ });
211
+
212
+ google.maps.event.addDomListener(this.div_, "mouseover", function () {
213
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
214
+ /**
215
+ * This event is fired when the mouse moves over a cluster marker.
216
+ * @name MarkerClusterer#mouseover
217
+ * @param {Cluster} c The cluster that the mouse moved over.
218
+ * @event
219
+ */
220
+ google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_);
221
+ });
222
+
223
+ google.maps.event.addDomListener(this.icon_div_, "mouseover", function () {
224
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
225
+ /**
226
+ * This event is fired when the mouse moves over a cluster marker.
227
+ * @name MarkerClusterer#mouseover
228
+ * @param {Cluster} c The cluster that the mouse moved over.
229
+ * @event
230
+ */
231
+ google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_);
232
+ });
233
+
234
+ google.maps.event.addDomListener(this.div_, "mouseout", function () {
235
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
236
+ /**
237
+ * This event is fired when the mouse moves out of a cluster marker.
238
+ * @name MarkerClusterer#mouseout
239
+ * @param {Cluster} c The cluster that the mouse moved out of.
240
+ * @event
241
+ */
242
+ google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_);
243
+ });
244
+
245
+ google.maps.event.addDomListener(this.icon_div_, "mouseout", function () {
246
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
247
+ /**
248
+ * This event is fired when the mouse moves out of a cluster marker.
249
+ * @name MarkerClusterer#mouseout
250
+ * @param {Cluster} c The cluster that the mouse moved out of.
251
+ * @event
252
+ */
253
+ google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_);
254
+ });
255
+ };
256
+
257
+
258
+ /**
259
+ * Removes the icon from the DOM.
260
+ */
261
+ ClusterIcon.prototype.onRemove = function () {
262
+ //if (this.div_ && this.div_.parentNode) {
263
+ if (this.icon_div_ && this.icon_div_.parentNode) {
264
+ this.hide();
265
+ google.maps.event.removeListener(this.boundsChangedListener_);
266
+ google.maps.event.clearInstanceListeners(this.div_);
267
+ this.div_.parentNode.removeChild(this.div_);
268
+ this.div_ = null;
269
+ google.maps.event.clearInstanceListeners(this.icon_div_);
270
+ this.icon_div_.parentNode.removeChild(this.icon_div_);
271
+ this.icon_div_ = null;
272
+ }
273
+ };
274
+
275
+
276
+ /**
277
+ * Draws the icon.
278
+ */
279
+ ClusterIcon.prototype.draw = function () {
280
+ if (this.visible_) {
281
+ var pos = this.getPosFromLatLng_(this.center_);
282
+ this.div_.style.top = pos.y + "px";
283
+ this.div_.style.left = pos.x + "px";
284
+ this.icon_div_.style.top = pos.y + "px";
285
+ this.icon_div_.style.left = pos.x + "px";
286
+ this.renderIcon_(); // additional
287
+ }
288
+ };
289
+
290
+
291
+ /**
292
+ * Hides the icon.
293
+ */
294
+ ClusterIcon.prototype.hide = function () {
295
+ if (this.div_) {
296
+ this.div_.style.display = "none";
297
+ }
298
+ if (this.icon_div_) {
299
+ this.icon_div_.style.display = "none";
300
+ }
301
+ this.visible_ = false;
302
+ };
303
+
304
+
305
+ /**
306
+ * Positions and shows the icon.
307
+ */
308
+ ClusterIcon.prototype.show = function () {
309
+ if (this.div_) {
310
+ //var img = "";
311
+ // NOTE: values must be specified in px units
312
+ var bp = this.backgroundPosition_.split(" ");
313
+ var spriteH = parseInt(bp[0].replace(/^\s+|\s+$/g, ""), 10);
314
+ var spriteV = parseInt(bp[1].replace(/^\s+|\s+$/g, ""), 10);
315
+ var pos = this.getPosFromLatLng_(this.center_);
316
+ this.div_.style.cssText = this.createCss(pos);
317
+ //img = "<img src='" + this.url_ + "' style='position: absolute; top: " + spriteV + "px; left: " + spriteH + "px; ";
318
+ //if (!this.cluster_.getMarkerClusterer().enableRetinaIcons_) {
319
+ // img += "clip: rect(" + (-1 * spriteV) + "px, " + ((-1 * spriteH) + this.width_) + "px, " +
320
+ // ((-1 * spriteV) + this.height_) + "px, " + (-1 * spriteH) + "px);";
321
+ //}
322
+ //img += "'>";
323
+ //this.div_.innerHTML = img + "<div style='" +
324
+ // "position: absolute;" +
325
+ // "top: " + this.anchorText_[0] + "px;" +
326
+ // "left: " + this.anchorText_[1] + "px;" +
327
+ // "color: " + this.textColor_ + ";" +
328
+ // "font-size: " + this.textSize_ + "px;" +
329
+ // "font-family: " + this.fontFamily_ + ";" +
330
+ // "font-weight: " + this.fontWeight_ + ";" +
331
+ // "font-style: " + this.fontStyle_ + ";" +
332
+ // "text-decoration: " + this.textDecoration_ + ";" +
333
+ // "text-align: center;" +
334
+ // "width: " + this.width_ + "px;" +
335
+ // "line-height:" + this.height_ + "px;" +
336
+ // "'>" + this.sums_.text + "</div>";
337
+ this.div_.innerHTML = "<div style='" +
338
+ "position: absolute;" +
339
+ "top: " + this.anchorText_[0] + "px;" +
340
+ "left: " + this.anchorText_[1] + "px;" +
341
+ "color: " + this.textColor_ + ";" +
342
+ "font-size: " + this.textSize_ + "px;" +
343
+ "font-family: " + this.fontFamily_ + ";" +
344
+ "font-weight: " + this.fontWeight_ + ";" +
345
+ "font-style: " + this.fontStyle_ + ";" +
346
+ "text-decoration: " + this.textDecoration_ + ";" +
347
+ "text-align: center;" +
348
+ "width: " + this.width_ + "px;" +
349
+ "line-height:" + this.height_ + "px;" +
350
+ "'>" + this.sums_.text + "</div>";
351
+ if (typeof this.sums_.title === "undefined" || this.sums_.title === "") {
352
+ this.div_.title = this.cluster_.getMarkerClusterer().getTitle();
353
+ } else {
354
+ this.div_.title = this.sums_.title;
355
+ }
356
+ this.div_.style.display = "";
357
+ }
358
+ if (this.icon_div_) {
359
+ // NOTE: values must be specified in px units
360
+ var bp = this.backgroundPosition_.split(" ");
361
+ var spriteH = parseInt(bp[0].replace(/^\s+|\s+$/g, ""), 10);
362
+ var spriteV = parseInt(bp[1].replace(/^\s+|\s+$/g, ""), 10);
363
+ var pos = this.getPosFromLatLng_(this.center_);
364
+ this.icon_div_.style.cssText = this.createCss(pos);
365
+ this.icon_div_.style.display = "";
366
+
367
+ // redner pie chart (initial infographic chart)
368
+ this.renderIcon_();
369
+ }
370
+ this.visible_ = true;
371
+ };
372
+
373
+ ClusterIcon.prototype.renderIcon_ = function() {
374
+ var clusterIconData = this.cluster_.getIconData();
375
+ var clusterLegend = this.cluster_.getMarkerClusterer().getLegend();
376
+
377
+ var dataArray = [['Title', 'Count']];
378
+ var iconColorsSeq = [];
379
+
380
+ for (var key in clusterIconData) {
381
+ if (clusterIconData.hasOwnProperty(key)) {
382
+ var dataRow = [];
383
+ dataRow.push(key);
384
+ dataRow.push(clusterIconData[key]);
385
+ dataArray.push(dataRow);
386
+ iconColorsSeq.push(clusterLegend[key]);
387
+ }
388
+
389
+ }
390
+
391
+ var data = google.visualization.arrayToDataTable(dataArray);
392
+ var options = {
393
+ fontSize: 10,
394
+ backgroundColor: 'transparent',
395
+ legend: 'none',
396
+ pieHole: 0.5,
397
+ tooltip: {text: 'both'},
398
+ colors: iconColorsSeq,
399
+ pieSliceTextStyle: {
400
+ color: 'black',
401
+ fontSize: 8
402
+ }
403
+ };
404
+
405
+ var icon = new google.visualization.PieChart(this.icon_div_);
406
+ icon.draw(data, options);
407
+ }
408
+
409
+
410
+ /**
411
+ * Sets the icon styles to the appropriate element in the styles array.
412
+ *
413
+ * @param {ClusterIconInfo} sums The icon label text and styles index.
414
+ */
415
+ ClusterIcon.prototype.useStyle = function (sums) {
416
+ this.sums_ = sums;
417
+ var index = Math.max(0, sums.index - 1);
418
+ index = Math.min(this.styles_.length - 1, index);
419
+ var style = this.styles_[index];
420
+ this.url_ = style.url;
421
+ this.height_ = style.height;
422
+ this.width_ = style.width;
423
+ this.anchorText_ = style.anchorText || [0, 0];
424
+ this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)];
425
+ this.textColor_ = style.textColor || "black";
426
+ this.textSize_ = style.textSize || 11;
427
+ this.textDecoration_ = style.textDecoration || "none";
428
+ this.fontWeight_ = style.fontWeight || "bold";
429
+ this.fontStyle_ = style.fontStyle || "normal";
430
+ this.fontFamily_ = style.fontFamily || "Arial,sans-serif";
431
+ this.backgroundPosition_ = style.backgroundPosition || "0 0";
432
+ };
433
+
434
+
435
+ /**
436
+ * Sets the position at which to center the icon.
437
+ *
438
+ * @param {google.maps.LatLng} center The latlng to set as the center.
439
+ */
440
+ ClusterIcon.prototype.setCenter = function (center) {
441
+ this.center_ = center;
442
+ };
443
+
444
+
445
+ /**
446
+ * Creates the cssText style parameter based on the position of the icon.
447
+ *
448
+ * @param {google.maps.Point} pos The position of the icon.
449
+ * @return {string} The CSS style text.
450
+ */
451
+ ClusterIcon.prototype.createCss = function (pos) {
452
+ var style = [];
453
+ style.push("cursor: pointer;");
454
+ style.push("position: absolute; top: " + pos.y + "px; left: " + pos.x + "px;");
455
+ style.push("width: " + this.width_ + "px; height: " + this.height_ + "px;");
456
+ return style.join("");
457
+ };
458
+
459
+
460
+ /**
461
+ * Returns the position at which to place the DIV depending on the latlng.
462
+ *
463
+ * @param {google.maps.LatLng} latlng The position in latlng.
464
+ * @return {google.maps.Point} The position in pixels.
465
+ */
466
+ ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) {
467
+ var pos = this.getProjection().fromLatLngToDivPixel(latlng);
468
+ pos.x -= this.anchorIcon_[1];
469
+ pos.y -= this.anchorIcon_[0];
470
+ pos.x = parseInt(pos.x, 10);
471
+ pos.y = parseInt(pos.y, 10);
472
+ return pos;
473
+ };
474
+
475
+
476
+ /**
477
+ * Creates a single cluster that manages a group of proximate markers.
478
+ * Used internally, do not call this constructor directly.
479
+ * @constructor
480
+ * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this
481
+ * cluster is associated.
482
+ */
483
+ function Cluster(mc) {
484
+ this.markerClusterer_ = mc;
485
+ this.map_ = mc.getMap();
486
+ this.legend_ = this.markerClusterer_.getLegend();
487
+ this.gridSize_ = mc.getGridSize();
488
+ this.minClusterSize_ = mc.getMinimumClusterSize();
489
+ this.averageCenter_ = mc.getAverageCenter();
490
+ this.markers_ = [];
491
+ this.center_ = null;
492
+ this.bounds_ = null;
493
+ this.iconData_ = {};
494
+
495
+ // Initialize icon slice values for the cluster icon
496
+ for (var key in this.legend_) {
497
+ if (this.legend_.hasOwnProperty(key)) {
498
+ this.iconData_[key] = this.legend_[key];
499
+ this.iconData_[key] = 0;
500
+ }
501
+ }
502
+
503
+ this.clusterIcon_ = new ClusterIcon(this, mc.getStyles());
504
+ }
505
+
506
+ Cluster.prototype.getIconData = function () {
507
+ return this.iconData_;
508
+ };
509
+
510
+ /**
511
+ * Returns the number of markers managed by the cluster. You can call this from
512
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
513
+ * for the <code>MarkerClusterer</code> object.
514
+ *
515
+ * @return {number} The number of markers in the cluster.
516
+ */
517
+ Cluster.prototype.getSize = function () {
518
+ return this.markers_.length;
519
+ };
520
+
521
+
522
+ /**
523
+ * Returns the array of markers managed by the cluster. You can call this from
524
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
525
+ * for the <code>MarkerClusterer</code> object.
526
+ *
527
+ * @return {Array} The array of markers in the cluster.
528
+ */
529
+ Cluster.prototype.getMarkers = function () {
530
+ return this.markers_;
531
+ };
532
+
533
+
534
+ /**
535
+ * Returns the center of the cluster. You can call this from
536
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
537
+ * for the <code>MarkerClusterer</code> object.
538
+ *
539
+ * @return {google.maps.LatLng} The center of the cluster.
540
+ */
541
+ Cluster.prototype.getCenter = function () {
542
+ return this.center_;
543
+ };
544
+
545
+
546
+ /**
547
+ * Returns the map with which the cluster is associated.
548
+ *
549
+ * @return {google.maps.Map} The map.
550
+ * @ignore
551
+ */
552
+ Cluster.prototype.getMap = function () {
553
+ return this.map_;
554
+ };
555
+
556
+
557
+ /**
558
+ * Returns the <code>MarkerClusterer</code> object with which the cluster is associated.
559
+ *
560
+ * @return {MarkerClusterer} The associated marker clusterer.
561
+ * @ignore
562
+ */
563
+ Cluster.prototype.getMarkerClusterer = function () {
564
+ return this.markerClusterer_;
565
+ };
566
+
567
+
568
+ /**
569
+ * Returns the bounds of the cluster.
570
+ *
571
+ * @return {google.maps.LatLngBounds} the cluster bounds.
572
+ * @ignore
573
+ */
574
+ Cluster.prototype.getBounds = function () {
575
+ var i;
576
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
577
+ var markers = this.getMarkers();
578
+ for (i = 0; i < markers.length; i++) {
579
+ bounds.extend(markers[i].getPosition());
580
+ }
581
+ return bounds;
582
+ };
583
+
584
+
585
+ /**
586
+ * Removes the cluster from the map.
587
+ *
588
+ * @ignore
589
+ */
590
+ Cluster.prototype.remove = function () {
591
+ this.clusterIcon_.setMap(null);
592
+ this.markers_ = [];
593
+ delete this.markers_;
594
+ };
595
+
596
+
597
+ /**
598
+ * Adds a marker to the cluster.
599
+ *
600
+ * @param {google.maps.Marker} marker The marker to be added.
601
+ * @return {boolean} True if the marker was added.
602
+ * @ignore
603
+ */
604
+ Cluster.prototype.addMarker = function (marker) {
605
+ var i;
606
+ var mCount;
607
+ var mz;
608
+
609
+ if (this.isMarkerAlreadyAdded_(marker)) {
610
+ return false;
611
+ }
612
+
613
+ if (!this.center_) {
614
+ this.center_ = marker.getPosition();
615
+ this.calculateBounds_();
616
+ } else {
617
+ if (this.averageCenter_) {
618
+ var l = this.markers_.length + 1;
619
+ var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l;
620
+ var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l;
621
+ this.center_ = new google.maps.LatLng(lat, lng);
622
+ this.calculateBounds_();
623
+ }
624
+ }
625
+
626
+ marker.isAdded = true;
627
+ this.markers_.push(marker);
628
+
629
+ //infographics
630
+ this.iconData_[marker.getTitle()]++; //Hassan
631
+
632
+ mCount = this.markers_.length;
633
+ mz = this.markerClusterer_.getMaxZoom();
634
+ if (mz !== null && this.map_.getZoom() > mz) {
635
+ // Zoomed in past max zoom, so show the marker.
636
+ if (marker.getMap() !== this.map_) {
637
+ marker.setMap(this.map_);
638
+ }
639
+ } else if (mCount < this.minClusterSize_) {
640
+ // Min cluster size not reached so show the marker.
641
+ if (marker.getMap() !== this.map_) {
642
+ marker.setMap(this.map_);
643
+ }
644
+ } else if (mCount === this.minClusterSize_) {
645
+ // Hide the markers that were showing.
646
+ for (i = 0; i < mCount; i++) {
647
+ this.markers_[i].setMap(null);
648
+ }
649
+ } else {
650
+ marker.setMap(null);
651
+ }
652
+
653
+ this.updateIcon_();
654
+ return true;
655
+ };
656
+
657
+
658
+ /**
659
+ * Determines if a marker lies within the cluster's bounds.
660
+ *
661
+ * @param {google.maps.Marker} marker The marker to check.
662
+ * @return {boolean} True if the marker lies in the bounds.
663
+ * @ignore
664
+ */
665
+ Cluster.prototype.isMarkerInClusterBounds = function (marker) {
666
+ return this.bounds_.contains(marker.getPosition());
667
+ };
668
+
669
+
670
+ /**
671
+ * Calculates the extended bounds of the cluster with the grid.
672
+ */
673
+ Cluster.prototype.calculateBounds_ = function () {
674
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
675
+ this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
676
+ };
677
+
678
+
679
+ /**
680
+ * Updates the cluster icon.
681
+ */
682
+ Cluster.prototype.updateIcon_ = function () {
683
+ var mCount = this.markers_.length;
684
+ var mz = this.markerClusterer_.getMaxZoom();
685
+
686
+ if (mz !== null && this.map_.getZoom() > mz) {
687
+ this.clusterIcon_.hide();
688
+ return;
689
+ }
690
+
691
+ if (mCount < this.minClusterSize_) {
692
+ // Min cluster size not yet reached.
693
+ this.clusterIcon_.hide();
694
+ return;
695
+ }
696
+
697
+ var numStyles = this.markerClusterer_.getStyles().length;
698
+ var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
699
+ this.clusterIcon_.setCenter(this.center_);
700
+ this.clusterIcon_.useStyle(sums);
701
+ this.clusterIcon_.show();
702
+ };
703
+
704
+
705
+ /**
706
+ * Determines if a marker has already been added to the cluster.
707
+ *
708
+ * @param {google.maps.Marker} marker The marker to check.
709
+ * @return {boolean} True if the marker has already been added.
710
+ */
711
+ Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) {
712
+ var i;
713
+ if (this.markers_.indexOf) {
714
+ return this.markers_.indexOf(marker) !== -1;
715
+ } else {
716
+ for (i = 0; i < this.markers_.length; i++) {
717
+ if (marker === this.markers_[i]) {
718
+ return true;
719
+ }
720
+ }
721
+ }
722
+ return false;
723
+ };
724
+
725
+
726
+ /**
727
+ * @name MarkerClustererOptions
728
+ * @class This class represents the optional parameter passed to
729
+ * the {@link MarkerClusterer} constructor.
730
+ * @property {Hash} {legend} An array of infogrphic legend.
731
+ * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square.
732
+ * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or
733
+ * <code>null</code> if clustering is to be enabled at all zoom levels.
734
+ * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is
735
+ * clicked. You may want to set this to <code>false</code> if you have installed a handler
736
+ * for the <code>click</code> event and it deals with zooming on its own.
737
+ * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be
738
+ * the average position of all markers in the cluster. If set to <code>false</code>, the
739
+ * cluster marker is positioned at the location of the first marker added to the cluster.
740
+ * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster
741
+ * before the markers are hidden and a cluster marker appears.
742
+ * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You
743
+ * may want to set this to <code>true</code> to ensure that hidden markers are not included
744
+ * in the marker count that appears on a cluster marker (this count is the value of the
745
+ * <code>text</code> property of the result returned by the default <code>calculator</code>).
746
+ * If set to <code>true</code> and you change the visibility of a marker being clustered, be
747
+ * sure to also call <code>MarkerClusterer.repaint()</code>.
748
+ * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster
749
+ * marker. (Alternatively, you can use a custom <code>calculator</code> function to specify a
750
+ * different tooltip for each cluster marker.)
751
+ * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine
752
+ * the text to be displayed on a cluster marker and the index indicating which style to use
753
+ * for the cluster marker. The input parameters for the function are (1) the array of markers
754
+ * represented by a cluster marker and (2) the number of cluster icon styles. It returns a
755
+ * {@link ClusterIconInfo} object. The default <code>calculator</code> returns a
756
+ * <code>text</code> property which is the number of markers in the cluster and an
757
+ * <code>index</code> property which is one higher than the lowest integer such that
758
+ * <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles
759
+ * array, whichever is less. The <code>styles</code> array element used has an index of
760
+ * <code>index</code> minus 1. For example, the default <code>calculator</code> returns a
761
+ * <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code>
762
+ * for a cluster icon representing 125 markers so the element used in the <code>styles</code>
763
+ * array is <code>2</code>. A <code>calculator</code> may also return a <code>title</code>
764
+ * property that contains the text of the tooltip to be used for the cluster marker. If
765
+ * <code>title</code> is not defined, the tooltip is set to the value of the <code>title</code>
766
+ * property for the MarkerClusterer.
767
+ * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles
768
+ * for the cluster markers. Use this class to define CSS styles that are not set up by the code
769
+ * that processes the <code>styles</code> array.
770
+ * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles
771
+ * of the cluster markers to be used. The element to be used to style a given cluster marker
772
+ * is determined by the function defined by the <code>calculator</code> property.
773
+ * The default is an array of {@link ClusterIconStyle} elements whose properties are derived
774
+ * from the values for <code>imagePath</code>, <code>imageExtension</code>, and
775
+ * <code>imageSizes</code>.
776
+ * @property {boolean} [enableRetinaIcons=false] Whether to allow the use of cluster icons that
777
+ * have sizes that are some multiple (typically double) of their actual display size. Icons such
778
+ * as these look better when viewed on high-resolution monitors such as Apple's Retina displays.
779
+ * Note: if this property is <code>true</code>, sprites cannot be used as cluster icons.
780
+ * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the
781
+ * number of markers to be processed in a single batch when using a browser other than
782
+ * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead).
783
+ * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is
784
+ * being used, markers are processed in several batches with a small delay inserted between
785
+ * each batch in an attempt to avoid Javascript timeout errors. Set this property to the
786
+ * number of markers to be processed in a single batch; select as high a number as you can
787
+ * without causing a timeout error in the browser. This number might need to be as low as 100
788
+ * if 15,000 markers are being managed, for example.
789
+ * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH]
790
+ * The full URL of the root name of the group of image files to use for cluster icons.
791
+ * The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code>
792
+ * where n is the image file number (1, 2, etc.).
793
+ * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION]
794
+ * The extension name for the cluster icon image files (e.g., <code>"png"</code> or
795
+ * <code>"jpg"</code>).
796
+ * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES]
797
+ * An array of numbers containing the widths of the group of
798
+ * <code>imagePath</code>n.<code>imageExtension</code> image files.
799
+ * (The images are assumed to be square.)
800
+ */
801
+ /**
802
+ * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}.
803
+ * @constructor
804
+ * @extends google.maps.OverlayView
805
+ * @param {google.maps.Map} map The Google map to attach to.
806
+ * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster.
807
+ * @param {MarkerClustererOptions} [opt_options] The optional parameters.
808
+ */
809
+ function MarkerClusterer(map, opt_markers, opt_options) {
810
+ // MarkerClusterer implements google.maps.OverlayView interface. We use the
811
+ // extend function to extend MarkerClusterer with google.maps.OverlayView
812
+ // because it might not always be available when the code is defined so we
813
+ // look for it at the last possible moment. If it doesn't exist now then
814
+ // there is no point going ahead :)
815
+ this.extend(MarkerClusterer, google.maps.OverlayView);
816
+
817
+ opt_markers = opt_markers || [];
818
+ opt_options = opt_options || {};
819
+
820
+ this.markers_ = [];
821
+ this.clusters_ = [];
822
+ this.listeners_ = [];
823
+ this.activeMap_ = null;
824
+ this.ready_ = false;
825
+
826
+ this.legend_ = opt_options.legend || {};
827
+ this.gridSize_ = opt_options.gridSize || 60;
828
+ this.minClusterSize_ = opt_options.minimumClusterSize || 2;
829
+ this.maxZoom_ = opt_options.maxZoom || null;
830
+ this.styles_ = opt_options.styles || [];
831
+ this.title_ = opt_options.title || "";
832
+ this.zoomOnClick_ = true;
833
+ if (opt_options.zoomOnClick !== undefined) {
834
+ this.zoomOnClick_ = opt_options.zoomOnClick;
835
+ }
836
+ this.averageCenter_ = false;
837
+ if (opt_options.averageCenter !== undefined) {
838
+ this.averageCenter_ = opt_options.averageCenter;
839
+ }
840
+ this.ignoreHidden_ = false;
841
+ if (opt_options.ignoreHidden !== undefined) {
842
+ this.ignoreHidden_ = opt_options.ignoreHidden;
843
+ }
844
+ this.enableRetinaIcons_ = false;
845
+ if (opt_options.enableRetinaIcons !== undefined) {
846
+ this.enableRetinaIcons_ = opt_options.enableRetinaIcons;
847
+ }
848
+ this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH;
849
+ this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION;
850
+ this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES;
851
+ this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR;
852
+ this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE;
853
+ this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE;
854
+ this.clusterClass_ = opt_options.clusterClass || "cluster";
855
+
856
+ if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) {
857
+ // Try to avoid IE timeout when processing a huge number of markers:
858
+ this.batchSize_ = this.batchSizeIE_;
859
+ }
860
+
861
+ this.setupStyles_();
862
+
863
+ this.addMarkers(opt_markers, true);
864
+ this.setMap(map); // Note: this causes onAdd to be called
865
+ }
866
+
867
+
868
+ /**
869
+ * Implementation of the onAdd interface method.
870
+ * @ignore
871
+ */
872
+ MarkerClusterer.prototype.onAdd = function () {
873
+ var cMarkerClusterer = this;
874
+
875
+ this.activeMap_ = this.getMap();
876
+ this.ready_ = true;
877
+
878
+ this.repaint();
879
+
880
+ // Add the map event listeners
881
+ this.listeners_ = [
882
+ google.maps.event.addListener(this.getMap(), "zoom_changed", function () {
883
+ cMarkerClusterer.resetViewport_(false);
884
+ // Workaround for this Google bug: when map is at level 0 and "-" of
885
+ // zoom slider is clicked, a "zoom_changed" event is fired even though
886
+ // the map doesn't zoom out any further. In this situation, no "idle"
887
+ // event is triggered so the cluster markers that have been removed
888
+ // do not get redrawn. Same goes for a zoom in at maxZoom.
889
+ if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) {
890
+ google.maps.event.trigger(this, "idle");
891
+ }
892
+ }),
893
+ google.maps.event.addListener(this.getMap(), "idle", function () {
894
+ cMarkerClusterer.redraw_();
895
+ })
896
+ ];
897
+ };
898
+
899
+
900
+ /**
901
+ * Implementation of the onRemove interface method.
902
+ * Removes map event listeners and all cluster icons from the DOM.
903
+ * All managed markers are also put back on the map.
904
+ * @ignore
905
+ */
906
+ MarkerClusterer.prototype.onRemove = function () {
907
+ var i;
908
+
909
+ // Put all the managed markers back on the map:
910
+ for (i = 0; i < this.markers_.length; i++) {
911
+ if (this.markers_[i].getMap() !== this.activeMap_) {
912
+ this.markers_[i].setMap(this.activeMap_);
913
+ }
914
+ }
915
+
916
+ // Remove all clusters:
917
+ for (i = 0; i < this.clusters_.length; i++) {
918
+ this.clusters_[i].remove();
919
+ }
920
+ this.clusters_ = [];
921
+
922
+ // Remove map event listeners:
923
+ for (i = 0; i < this.listeners_.length; i++) {
924
+ google.maps.event.removeListener(this.listeners_[i]);
925
+ }
926
+ this.listeners_ = [];
927
+
928
+ this.activeMap_ = null;
929
+ this.ready_ = false;
930
+ };
931
+
932
+
933
+ /**
934
+ * Implementation of the draw interface method.
935
+ * @ignore
936
+ */
937
+ MarkerClusterer.prototype.draw = function () {};
938
+
939
+
940
+ /**
941
+ * Sets up the styles object.
942
+ */
943
+ /*
944
+ MarkerClusterer.prototype.setupStyles_ = function () {
945
+ var i, size;
946
+ var imagePath = [];
947
+ if (this.styles_.length > 0) {
948
+ return;
949
+ }
950
+
951
+ <% (0..5).each do |i| %>
952
+ imagePath.push("<%= image_path('m' + (i + 1).to_s + '.png') %>");
953
+ <% end %>
954
+ for (i = 0; i < this.imageSizes_.length; i++) {
955
+ size = this.imageSizes_[i];
956
+ this.styles_.push({
957
+ url: imagePath[i],
958
+ height: size,
959
+ width: size
960
+ });
961
+ }
962
+ }
963
+ */
964
+
965
+ MarkerClusterer.prototype.setupStyles_ = function () {
966
+ var i, size;
967
+ var imagePath = [];
968
+ if (this.styles_.length > 0) {
969
+ return;
970
+ }
971
+
972
+ for (i = 0; i < this.imageSizes_.length; i++) {
973
+ size = this.imageSizes_[i];
974
+ this.styles_.push({
975
+ url: this.imagePath_ + (i + 1) + "." + this.imageExtension_,
976
+ height: size,
977
+ width: size
978
+ });
979
+ }
980
+ };
981
+
982
+ /**
983
+ * Set up the legend of infographics
984
+ */
985
+
986
+ MarkerClusterer.prototype.setupLegend_ = function (markers) {
987
+ var colorSeries = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00",
988
+ "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067",
989
+ "#329262", "#5574a6", "#3b3eac", "#b77322", "#16d620", "#b91383", "#f4359e", "#9c5935", "#a9c413",
990
+ "#2a778d", "#668d1c", "#bea413", "#0c5922", "#743411"];
991
+
992
+ var markerSymbol = {
993
+ path: 'M256 14.316c-91.31 0-165.325 74.025-165.325 165.325 0.010 91.32 165.325 318.044 165.325 318.044s165.315-226.724 165.315-318.034c0.010-91.31-73.984-165.335-165.315-165.335zM256 245.494c-34.56 0-62.608-28.078-62.608-62.648 0-34.55 28.037-62.566 62.608-62.566 34.591 0 62.618 28.027 62.618 62.566 0 34.57-28.017 62.649-62.618 62.649z',
994
+ fillOpacity: 1.0,
995
+ scale: 0.065,
996
+ //strokeColor: 'white',
997
+ //strokeWeight: 1,
998
+ anchor: new google.maps.Point(250, 500)
999
+ };
1000
+
1001
+ //Check for user defined legend
1002
+ for (var key in this.legend_) {
1003
+ if (this.legend_.hasOwnProperty(key)) {
1004
+ var index = colorSeries.indexOf(this.legend_[key]);
1005
+ if (index > -1) {
1006
+ colorSeries.splice(index, 1);
1007
+ }
1008
+ }
1009
+ }
1010
+
1011
+ var colorIndex = 0;
1012
+ for (var i = 0, marker; marker = markers[i]; i++) {
1013
+ if (!(marker.title in this.legend_)) {
1014
+ this.legend_[marker.title] = (colorSeries[colorIndex]);
1015
+ markerSymbol["fillColor"] = (colorSeries[colorIndex]);
1016
+ marker.setIcon(markerSymbol);
1017
+ colorIndex++;
1018
+ }
1019
+ else {
1020
+ markerSymbol["fillColor"] = this.legend_[marker.title];
1021
+ marker.setIcon(markerSymbol);
1022
+ }
1023
+ }
1024
+
1025
+ var legend_div = document.createElement('div');
1026
+ legend_div.style.cssText = "margin-right: 5px; background-color: rgba(255, 255, 255, 0.9); padding: 10px; width: 123px";
1027
+ this.map_.controls[google.maps.ControlPosition.RIGHT_TOP].push(legend_div);
1028
+
1029
+ for (var title in this.legend_) {
1030
+ var color = this.legend_[title];
1031
+ var color_div = document.createElement('div');
1032
+ color_div.style.cssText = "float: left; margin:0; overflow:hidden; background-color:" + color + "; width: 12px; height: 12px;";
1033
+ legend_div.appendChild(color_div);
1034
+
1035
+ var title_div = document.createElement('div');
1036
+ title_div.innerHTML = title;
1037
+ title_div.style.cssText = "padding-bottom: 5px; padding-left: 5%; float: left; margin-left:0; width:80%; overflow:hidden;";
1038
+ legend_div.appendChild(title_div);
1039
+ }
1040
+ //var panes = this.getPanes();
1041
+ //panes.overlayMouseTarget.appendChild(this.div_);
1042
+ // console.log(markers);
1043
+ };
1044
+
1045
+ /**
1046
+ * Fits the map to the bounds of the markers managed by the clusterer.
1047
+ */
1048
+ MarkerClusterer.prototype.fitMapToMarkers = function () {
1049
+ var i;
1050
+ var markers = this.getMarkers();
1051
+ var bounds = new google.maps.LatLngBounds();
1052
+ for (i = 0; i < markers.length; i++) {
1053
+ bounds.extend(markers[i].getPosition());
1054
+ }
1055
+
1056
+ this.getMap().fitBounds(bounds);
1057
+ };
1058
+
1059
+ /**
1060
+ * Returns the value of the <code>legend</code> property.
1061
+ *
1062
+ * @return {Objects} The grid size.
1063
+ */
1064
+ MarkerClusterer.prototype.getLegend = function () {
1065
+ return this.legend_;
1066
+ };
1067
+
1068
+ /**
1069
+ * Sets the value of the <code>legend</code> property.
1070
+ *
1071
+ * @param {Hash} {legend} The legend of infographics.
1072
+ */
1073
+ MarkerClusterer.prototype.setLegend = function (legend) {
1074
+ this.legend_ = legend;
1075
+ };
1076
+
1077
+ /**
1078
+ * Returns the value of the <code>gridSize</code> property.
1079
+ *
1080
+ * @return {number} The grid size.
1081
+ */
1082
+ MarkerClusterer.prototype.getGridSize = function () {
1083
+ return this.gridSize_;
1084
+ };
1085
+
1086
+
1087
+ /**
1088
+ * Sets the value of the <code>gridSize</code> property.
1089
+ *
1090
+ * @param {number} gridSize The grid size.
1091
+ */
1092
+ MarkerClusterer.prototype.setGridSize = function (gridSize) {
1093
+ this.gridSize_ = gridSize;
1094
+ };
1095
+
1096
+
1097
+ /**
1098
+ * Returns the value of the <code>minimumClusterSize</code> property.
1099
+ *
1100
+ * @return {number} The minimum cluster size.
1101
+ */
1102
+ MarkerClusterer.prototype.getMinimumClusterSize = function () {
1103
+ return this.minClusterSize_;
1104
+ };
1105
+
1106
+ /**
1107
+ * Sets the value of the <code>minimumClusterSize</code> property.
1108
+ *
1109
+ * @param {number} minimumClusterSize The minimum cluster size.
1110
+ */
1111
+ MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) {
1112
+ this.minClusterSize_ = minimumClusterSize;
1113
+ };
1114
+
1115
+
1116
+ /**
1117
+ * Returns the value of the <code>maxZoom</code> property.
1118
+ *
1119
+ * @return {number} The maximum zoom level.
1120
+ */
1121
+ MarkerClusterer.prototype.getMaxZoom = function () {
1122
+ return this.maxZoom_;
1123
+ };
1124
+
1125
+
1126
+ /**
1127
+ * Sets the value of the <code>maxZoom</code> property.
1128
+ *
1129
+ * @param {number} maxZoom The maximum zoom level.
1130
+ */
1131
+ MarkerClusterer.prototype.setMaxZoom = function (maxZoom) {
1132
+ this.maxZoom_ = maxZoom;
1133
+ };
1134
+
1135
+
1136
+ /**
1137
+ * Returns the value of the <code>styles</code> property.
1138
+ *
1139
+ * @return {Array} The array of styles defining the cluster markers to be used.
1140
+ */
1141
+ MarkerClusterer.prototype.getStyles = function () {
1142
+ return this.styles_;
1143
+ };
1144
+
1145
+
1146
+ /**
1147
+ * Sets the value of the <code>styles</code> property.
1148
+ *
1149
+ * @param {Array.<ClusterIconStyle>} styles The array of styles to use.
1150
+ */
1151
+ MarkerClusterer.prototype.setStyles = function (styles) {
1152
+ this.styles_ = styles;
1153
+ };
1154
+
1155
+
1156
+ /**
1157
+ * Returns the value of the <code>title</code> property.
1158
+ *
1159
+ * @return {string} The content of the title text.
1160
+ */
1161
+ MarkerClusterer.prototype.getTitle = function () {
1162
+ return this.title_;
1163
+ };
1164
+
1165
+
1166
+ /**
1167
+ * Sets the value of the <code>title</code> property.
1168
+ *
1169
+ * @param {string} title The value of the title property.
1170
+ */
1171
+ MarkerClusterer.prototype.setTitle = function (title) {
1172
+ this.title_ = title;
1173
+ };
1174
+
1175
+
1176
+ /**
1177
+ * Returns the value of the <code>zoomOnClick</code> property.
1178
+ *
1179
+ * @return {boolean} True if zoomOnClick property is set.
1180
+ */
1181
+ MarkerClusterer.prototype.getZoomOnClick = function () {
1182
+ return this.zoomOnClick_;
1183
+ };
1184
+
1185
+
1186
+ /**
1187
+ * Sets the value of the <code>zoomOnClick</code> property.
1188
+ *
1189
+ * @param {boolean} zoomOnClick The value of the zoomOnClick property.
1190
+ */
1191
+ MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) {
1192
+ this.zoomOnClick_ = zoomOnClick;
1193
+ };
1194
+
1195
+
1196
+ /**
1197
+ * Returns the value of the <code>averageCenter</code> property.
1198
+ *
1199
+ * @return {boolean} True if averageCenter property is set.
1200
+ */
1201
+ MarkerClusterer.prototype.getAverageCenter = function () {
1202
+ return this.averageCenter_;
1203
+ };
1204
+
1205
+
1206
+ /**
1207
+ * Sets the value of the <code>averageCenter</code> property.
1208
+ *
1209
+ * @param {boolean} averageCenter The value of the averageCenter property.
1210
+ */
1211
+ MarkerClusterer.prototype.setAverageCenter = function (averageCenter) {
1212
+ this.averageCenter_ = averageCenter;
1213
+ };
1214
+
1215
+
1216
+ /**
1217
+ * Returns the value of the <code>ignoreHidden</code> property.
1218
+ *
1219
+ * @return {boolean} True if ignoreHidden property is set.
1220
+ */
1221
+ MarkerClusterer.prototype.getIgnoreHidden = function () {
1222
+ return this.ignoreHidden_;
1223
+ };
1224
+
1225
+
1226
+ /**
1227
+ * Sets the value of the <code>ignoreHidden</code> property.
1228
+ *
1229
+ * @param {boolean} ignoreHidden The value of the ignoreHidden property.
1230
+ */
1231
+ MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) {
1232
+ this.ignoreHidden_ = ignoreHidden;
1233
+ };
1234
+
1235
+
1236
+ /**
1237
+ * Returns the value of the <code>enableRetinaIcons</code> property.
1238
+ *
1239
+ * @return {boolean} True if enableRetinaIcons property is set.
1240
+ */
1241
+ MarkerClusterer.prototype.getEnableRetinaIcons = function () {
1242
+ return this.enableRetinaIcons_;
1243
+ };
1244
+
1245
+
1246
+ /**
1247
+ * Sets the value of the <code>enableRetinaIcons</code> property.
1248
+ *
1249
+ * @param {boolean} enableRetinaIcons The value of the enableRetinaIcons property.
1250
+ */
1251
+ MarkerClusterer.prototype.setEnableRetinaIcons = function (enableRetinaIcons) {
1252
+ this.enableRetinaIcons_ = enableRetinaIcons;
1253
+ };
1254
+
1255
+
1256
+ /**
1257
+ * Returns the value of the <code>imageExtension</code> property.
1258
+ *
1259
+ * @return {string} The value of the imageExtension property.
1260
+ */
1261
+ MarkerClusterer.prototype.getImageExtension = function () {
1262
+ return this.imageExtension_;
1263
+ };
1264
+
1265
+
1266
+ /**
1267
+ * Sets the value of the <code>imageExtension</code> property.
1268
+ *
1269
+ * @param {string} imageExtension The value of the imageExtension property.
1270
+ */
1271
+ MarkerClusterer.prototype.setImageExtension = function (imageExtension) {
1272
+ this.imageExtension_ = imageExtension;
1273
+ };
1274
+
1275
+
1276
+ /**
1277
+ * Returns the value of the <code>imagePath</code> property.
1278
+ *
1279
+ * @return {string} The value of the imagePath property.
1280
+ */
1281
+ MarkerClusterer.prototype.getImagePath = function () {
1282
+ return this.imagePath_;
1283
+ };
1284
+
1285
+
1286
+ /**
1287
+ * Sets the value of the <code>imagePath</code> property.
1288
+ *
1289
+ * @param {string} imagePath The value of the imagePath property.
1290
+ */
1291
+ MarkerClusterer.prototype.setImagePath = function (imagePath) {
1292
+ this.imagePath_ = imagePath;
1293
+ };
1294
+
1295
+
1296
+ /**
1297
+ * Returns the value of the <code>imageSizes</code> property.
1298
+ *
1299
+ * @return {Array} The value of the imageSizes property.
1300
+ */
1301
+ MarkerClusterer.prototype.getImageSizes = function () {
1302
+ return this.imageSizes_;
1303
+ };
1304
+
1305
+
1306
+ /**
1307
+ * Sets the value of the <code>imageSizes</code> property.
1308
+ *
1309
+ * @param {Array} imageSizes The value of the imageSizes property.
1310
+ */
1311
+ MarkerClusterer.prototype.setImageSizes = function (imageSizes) {
1312
+ this.imageSizes_ = imageSizes;
1313
+ };
1314
+
1315
+
1316
+ /**
1317
+ * Returns the value of the <code>calculator</code> property.
1318
+ *
1319
+ * @return {function} the value of the calculator property.
1320
+ */
1321
+ MarkerClusterer.prototype.getCalculator = function () {
1322
+ return this.calculator_;
1323
+ };
1324
+
1325
+
1326
+ /**
1327
+ * Sets the value of the <code>calculator</code> property.
1328
+ *
1329
+ * @param {function(Array.<google.maps.Marker>, number)} calculator The value
1330
+ * of the calculator property.
1331
+ */
1332
+ MarkerClusterer.prototype.setCalculator = function (calculator) {
1333
+ this.calculator_ = calculator;
1334
+ };
1335
+
1336
+
1337
+ /**
1338
+ * Returns the value of the <code>batchSizeIE</code> property.
1339
+ *
1340
+ * @return {number} the value of the batchSizeIE property.
1341
+ */
1342
+ MarkerClusterer.prototype.getBatchSizeIE = function () {
1343
+ return this.batchSizeIE_;
1344
+ };
1345
+
1346
+
1347
+ /**
1348
+ * Sets the value of the <code>batchSizeIE</code> property.
1349
+ *
1350
+ * @param {number} batchSizeIE The value of the batchSizeIE property.
1351
+ */
1352
+ MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) {
1353
+ this.batchSizeIE_ = batchSizeIE;
1354
+ };
1355
+
1356
+
1357
+ /**
1358
+ * Returns the value of the <code>clusterClass</code> property.
1359
+ *
1360
+ * @return {string} the value of the clusterClass property.
1361
+ */
1362
+ MarkerClusterer.prototype.getClusterClass = function () {
1363
+ return this.clusterClass_;
1364
+ };
1365
+
1366
+
1367
+ /**
1368
+ * Sets the value of the <code>clusterClass</code> property.
1369
+ *
1370
+ * @param {string} clusterClass The value of the clusterClass property.
1371
+ */
1372
+ MarkerClusterer.prototype.setClusterClass = function (clusterClass) {
1373
+ this.clusterClass_ = clusterClass;
1374
+ };
1375
+
1376
+
1377
+ /**
1378
+ * Returns the array of markers managed by the clusterer.
1379
+ *
1380
+ * @return {Array} The array of markers managed by the clusterer.
1381
+ */
1382
+ MarkerClusterer.prototype.getMarkers = function () {
1383
+ return this.markers_;
1384
+ };
1385
+
1386
+
1387
+ /**
1388
+ * Returns the number of markers managed by the clusterer.
1389
+ *
1390
+ * @return {number} The number of markers.
1391
+ */
1392
+ MarkerClusterer.prototype.getTotalMarkers = function () {
1393
+ return this.markers_.length;
1394
+ };
1395
+
1396
+
1397
+ /**
1398
+ * Returns the current array of clusters formed by the clusterer.
1399
+ *
1400
+ * @return {Array} The array of clusters formed by the clusterer.
1401
+ */
1402
+ MarkerClusterer.prototype.getClusters = function () {
1403
+ return this.clusters_;
1404
+ };
1405
+
1406
+
1407
+ /**
1408
+ * Returns the number of clusters formed by the clusterer.
1409
+ *
1410
+ * @return {number} The number of clusters formed by the clusterer.
1411
+ */
1412
+ MarkerClusterer.prototype.getTotalClusters = function () {
1413
+ return this.clusters_.length;
1414
+ };
1415
+
1416
+
1417
+ /**
1418
+ * Adds a marker to the clusterer. The clusters are redrawn unless
1419
+ * <code>opt_nodraw</code> is set to <code>true</code>.
1420
+ *
1421
+ * @param {google.maps.Marker} marker The marker to add.
1422
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1423
+ */
1424
+ MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) {
1425
+ this.pushMarkerTo_(marker);
1426
+ if (!opt_nodraw) {
1427
+ this.redraw_();
1428
+ }
1429
+ };
1430
+
1431
+
1432
+ /**
1433
+ * Adds an array of markers to the clusterer. The clusters are redrawn unless
1434
+ * <code>opt_nodraw</code> is set to <code>true</code>.
1435
+ *
1436
+ * @param {Array.<google.maps.Marker>} markers The markers to add.
1437
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1438
+ */
1439
+ MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) {
1440
+ var key;
1441
+ for (key in markers) {
1442
+ if (markers.hasOwnProperty(key)) {
1443
+ this.pushMarkerTo_(markers[key]);
1444
+ }
1445
+ }
1446
+ if (!opt_nodraw) {
1447
+ this.redraw_();
1448
+ }
1449
+ };
1450
+
1451
+
1452
+ /**
1453
+ * Pushes a marker to the clusterer.
1454
+ *
1455
+ * @param {google.maps.Marker} marker The marker to add.
1456
+ */
1457
+ MarkerClusterer.prototype.pushMarkerTo_ = function (marker) {
1458
+ // If the marker is draggable add a listener so we can update the clusters on the dragend:
1459
+ if (marker.getDraggable()) {
1460
+ var cMarkerClusterer = this;
1461
+ google.maps.event.addListener(marker, "dragend", function () {
1462
+ if (cMarkerClusterer.ready_) {
1463
+ this.isAdded = false;
1464
+ cMarkerClusterer.repaint();
1465
+ }
1466
+ });
1467
+ }
1468
+ marker.isAdded = false;
1469
+ this.markers_.push(marker);
1470
+ };
1471
+
1472
+
1473
+ /**
1474
+ * Removes a marker from the cluster. The clusters are redrawn unless
1475
+ * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the
1476
+ * marker was removed from the clusterer.
1477
+ *
1478
+ * @param {google.maps.Marker} marker The marker to remove.
1479
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1480
+ * @return {boolean} True if the marker was removed from the clusterer.
1481
+ */
1482
+ MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) {
1483
+ var removed = this.removeMarker_(marker);
1484
+
1485
+ if (!opt_nodraw && removed) {
1486
+ this.repaint();
1487
+ }
1488
+
1489
+ return removed;
1490
+ };
1491
+
1492
+
1493
+ /**
1494
+ * Removes an array of markers from the cluster. The clusters are redrawn unless
1495
+ * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers
1496
+ * were removed from the clusterer.
1497
+ *
1498
+ * @param {Array.<google.maps.Marker>} markers The markers to remove.
1499
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1500
+ * @return {boolean} True if markers were removed from the clusterer.
1501
+ */
1502
+ MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) {
1503
+ var i, r;
1504
+ var removed = false;
1505
+
1506
+ for (i = 0; i < markers.length; i++) {
1507
+ r = this.removeMarker_(markers[i]);
1508
+ removed = removed || r;
1509
+ }
1510
+
1511
+ if (!opt_nodraw && removed) {
1512
+ this.repaint();
1513
+ }
1514
+
1515
+ return removed;
1516
+ };
1517
+
1518
+
1519
+ /**
1520
+ * Removes a marker and returns true if removed, false if not.
1521
+ *
1522
+ * @param {google.maps.Marker} marker The marker to remove
1523
+ * @return {boolean} Whether the marker was removed or not
1524
+ */
1525
+ MarkerClusterer.prototype.removeMarker_ = function (marker) {
1526
+ var i;
1527
+ var index = -1;
1528
+ if (this.markers_.indexOf) {
1529
+ index = this.markers_.indexOf(marker);
1530
+ } else {
1531
+ for (i = 0; i < this.markers_.length; i++) {
1532
+ if (marker === this.markers_[i]) {
1533
+ index = i;
1534
+ break;
1535
+ }
1536
+ }
1537
+ }
1538
+
1539
+ if (index === -1) {
1540
+ // Marker is not in our list of markers, so do nothing:
1541
+ return false;
1542
+ }
1543
+
1544
+ marker.setMap(null);
1545
+ this.markers_.splice(index, 1); // Remove the marker from the list of managed markers
1546
+ return true;
1547
+ };
1548
+
1549
+
1550
+ /**
1551
+ * Removes all clusters and markers from the map and also removes all markers
1552
+ * managed by the clusterer.
1553
+ */
1554
+ MarkerClusterer.prototype.clearMarkers = function () {
1555
+ this.resetViewport_(true);
1556
+ this.markers_ = [];
1557
+ };
1558
+
1559
+
1560
+ /**
1561
+ * Recalculates and redraws all the marker clusters from scratch.
1562
+ * Call this after changing any properties.
1563
+ */
1564
+ MarkerClusterer.prototype.repaint = function () {
1565
+ var oldClusters = this.clusters_.slice();
1566
+ this.clusters_ = [];
1567
+ this.resetViewport_(false);
1568
+ this.redraw_();
1569
+
1570
+ // Remove the old clusters.
1571
+ // Do it in a timeout to prevent blinking effect.
1572
+ setTimeout(function () {
1573
+ var i;
1574
+ for (i = 0; i < oldClusters.length; i++) {
1575
+ oldClusters[i].remove();
1576
+ }
1577
+ }, 0);
1578
+ };
1579
+
1580
+
1581
+ /**
1582
+ * Returns the current bounds extended by the grid size.
1583
+ *
1584
+ * @param {google.maps.LatLngBounds} bounds The bounds to extend.
1585
+ * @return {google.maps.LatLngBounds} The extended bounds.
1586
+ * @ignore
1587
+ */
1588
+ MarkerClusterer.prototype.getExtendedBounds = function (bounds) {
1589
+ var projection = this.getProjection();
1590
+
1591
+ // Turn the bounds into latlng.
1592
+ var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
1593
+ bounds.getNorthEast().lng());
1594
+ var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
1595
+ bounds.getSouthWest().lng());
1596
+
1597
+ // Convert the points to pixels and the extend out by the grid size.
1598
+ var trPix = projection.fromLatLngToDivPixel(tr);
1599
+ trPix.x += this.gridSize_;
1600
+ trPix.y -= this.gridSize_;
1601
+
1602
+ var blPix = projection.fromLatLngToDivPixel(bl);
1603
+ blPix.x -= this.gridSize_;
1604
+ blPix.y += this.gridSize_;
1605
+
1606
+ // Convert the pixel points back to LatLng
1607
+ var ne = projection.fromDivPixelToLatLng(trPix);
1608
+ var sw = projection.fromDivPixelToLatLng(blPix);
1609
+
1610
+ // Extend the bounds to contain the new bounds.
1611
+ bounds.extend(ne);
1612
+ bounds.extend(sw);
1613
+
1614
+ return bounds;
1615
+ };
1616
+
1617
+
1618
+ /**
1619
+ * Redraws all the clusters.
1620
+ */
1621
+ MarkerClusterer.prototype.redraw_ = function () {
1622
+ this.createClusters_(0);
1623
+ };
1624
+
1625
+
1626
+ /**
1627
+ * Removes all clusters from the map. The markers are also removed from the map
1628
+ * if <code>opt_hide</code> is set to <code>true</code>.
1629
+ *
1630
+ * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers
1631
+ * from the map.
1632
+ */
1633
+ MarkerClusterer.prototype.resetViewport_ = function (opt_hide) {
1634
+ var i, marker;
1635
+ // Remove all the clusters
1636
+ for (i = 0; i < this.clusters_.length; i++) {
1637
+ this.clusters_[i].remove();
1638
+ }
1639
+ this.clusters_ = [];
1640
+
1641
+ // Reset the markers to not be added and to be removed from the map.
1642
+ for (i = 0; i < this.markers_.length; i++) {
1643
+ marker = this.markers_[i];
1644
+ marker.isAdded = false;
1645
+ if (opt_hide) {
1646
+ marker.setMap(null);
1647
+ }
1648
+ }
1649
+ };
1650
+
1651
+
1652
+ /**
1653
+ * Calculates the distance between two latlng locations in km.
1654
+ *
1655
+ * @param {google.maps.LatLng} p1 The first lat lng point.
1656
+ * @param {google.maps.LatLng} p2 The second lat lng point.
1657
+ * @return {number} The distance between the two points in km.
1658
+ * @see http://www.movable-type.co.uk/scripts/latlong.html
1659
+ */
1660
+ MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) {
1661
+ var R = 6371; // Radius of the Earth in km
1662
+ var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
1663
+ var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
1664
+ var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
1665
+ Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
1666
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
1667
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1668
+ var d = R * c;
1669
+ return d;
1670
+ };
1671
+
1672
+
1673
+ /**
1674
+ * Determines if a marker is contained in a bounds.
1675
+ *
1676
+ * @param {google.maps.Marker} marker The marker to check.
1677
+ * @param {google.maps.LatLngBounds} bounds The bounds to check against.
1678
+ * @return {boolean} True if the marker is in the bounds.
1679
+ */
1680
+ MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) {
1681
+ return bounds.contains(marker.getPosition());
1682
+ };
1683
+
1684
+
1685
+ /**
1686
+ * Adds a marker to a cluster, or creates a new cluster.
1687
+ *
1688
+ * @param {google.maps.Marker} marker The marker to add.
1689
+ */
1690
+ MarkerClusterer.prototype.addToClosestCluster_ = function (marker) {
1691
+ var i, d, cluster, center;
1692
+ var distance = 40000; // Some large number
1693
+ var clusterToAddTo = null;
1694
+ for (i = 0; i < this.clusters_.length; i++) {
1695
+ cluster = this.clusters_[i];
1696
+ center = cluster.getCenter();
1697
+ if (center) {
1698
+ d = this.distanceBetweenPoints_(center, marker.getPosition());
1699
+ if (d < distance) {
1700
+ distance = d;
1701
+ clusterToAddTo = cluster;
1702
+ }
1703
+ }
1704
+ }
1705
+
1706
+ if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
1707
+ clusterToAddTo.addMarker(marker);
1708
+ } else {
1709
+ cluster = new Cluster(this);
1710
+ cluster.addMarker(marker);
1711
+ this.clusters_.push(cluster);
1712
+ }
1713
+ };
1714
+
1715
+
1716
+ /**
1717
+ * Creates the clusters. This is done in batches to avoid timeout errors
1718
+ * in some browsers when there is a huge number of markers.
1719
+ *
1720
+ * @param {number} iFirst The index of the first marker in the batch of
1721
+ * markers to be added to clusters.
1722
+ */
1723
+ MarkerClusterer.prototype.createClusters_ = function (iFirst) {
1724
+ var i, marker;
1725
+ var mapBounds;
1726
+ var cMarkerClusterer = this;
1727
+ if (!this.ready_) {
1728
+ return;
1729
+ }
1730
+
1731
+ // Cancel previous batch processing if we're working on the first batch:
1732
+ if (iFirst === 0) {
1733
+ /**
1734
+ * This event is fired when the <code>MarkerClusterer</code> begins
1735
+ * clustering markers.
1736
+ * @name MarkerClusterer#clusteringbegin
1737
+ * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
1738
+ * @event
1739
+ */
1740
+ google.maps.event.trigger(this, "clusteringbegin", this);
1741
+
1742
+ if (typeof this.timerRefStatic !== "undefined") {
1743
+ clearTimeout(this.timerRefStatic);
1744
+ delete this.timerRefStatic;
1745
+ }
1746
+ }
1747
+
1748
+ // Get our current map view bounds.
1749
+ // Create a new bounds object so we don't affect the map.
1750
+ //
1751
+ // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug:
1752
+ if (this.getMap().getZoom() > 3) {
1753
+ mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(),
1754
+ this.getMap().getBounds().getNorthEast());
1755
+ } else {
1756
+ mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625));
1757
+ }
1758
+ var bounds = this.getExtendedBounds(mapBounds);
1759
+
1760
+ var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length);
1761
+
1762
+ for (i = iFirst; i < iLast; i++) {
1763
+ marker = this.markers_[i];
1764
+ if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
1765
+ if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) {
1766
+ this.addToClosestCluster_(marker);
1767
+ }
1768
+ }
1769
+ }
1770
+
1771
+ if (iLast < this.markers_.length) {
1772
+ this.timerRefStatic = setTimeout(function () {
1773
+ cMarkerClusterer.createClusters_(iLast);
1774
+ }, 0);
1775
+ } else {
1776
+ delete this.timerRefStatic;
1777
+
1778
+ /**
1779
+ * This event is fired when the <code>MarkerClusterer</code> stops
1780
+ * clustering markers.
1781
+ * @name MarkerClusterer#clusteringend
1782
+ * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
1783
+ * @event
1784
+ */
1785
+ google.maps.event.trigger(this, "clusteringend", this);
1786
+ }
1787
+ };
1788
+
1789
+
1790
+ /**
1791
+ * Extends an object's prototype by another's.
1792
+ *
1793
+ * @param {Object} obj1 The object to be extended.
1794
+ * @param {Object} obj2 The object to extend with.
1795
+ * @return {Object} The new extended object.
1796
+ * @ignore
1797
+ */
1798
+ MarkerClusterer.prototype.extend = function (obj1, obj2) {
1799
+ return (function (object) {
1800
+ var property;
1801
+ for (property in object.prototype) {
1802
+ this.prototype[property] = object.prototype[property];
1803
+ }
1804
+ return this;
1805
+ }).apply(obj1, [obj2]);
1806
+ };
1807
+
1808
+
1809
+ /**
1810
+ * The default function for determining the label text and style
1811
+ * for a cluster icon.
1812
+ *
1813
+ * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster.
1814
+ * @param {number} numStyles The number of marker styles available.
1815
+ * @return {ClusterIconInfo} The information resource for the cluster.
1816
+ * @constant
1817
+ * @ignore
1818
+ */
1819
+ MarkerClusterer.CALCULATOR = function (markers, numStyles) {
1820
+ var index = 0;
1821
+ var title = "";
1822
+ var count = markers.length.toString();
1823
+
1824
+ var dv = count;
1825
+ while (dv !== 0) {
1826
+ dv = parseInt(dv / 10, 10);
1827
+ index++;
1828
+ }
1829
+
1830
+ index = Math.min(index, numStyles);
1831
+ return {
1832
+ text: count,
1833
+ index: index,
1834
+ title: title
1835
+ };
1836
+ };
1837
+
1838
+
1839
+ /**
1840
+ * The number of markers to process in one batch.
1841
+ *
1842
+ * @type {number}
1843
+ * @constant
1844
+ */
1845
+ MarkerClusterer.BATCH_SIZE = 2000;
1846
+
1847
+
1848
+ /**
1849
+ * The number of markers to process in one batch (IE only).
1850
+ *
1851
+ * @type {number}
1852
+ * @constant
1853
+ */
1854
+ MarkerClusterer.BATCH_SIZE_IE = 500;
1855
+
1856
+
1857
+ /**
1858
+ * The default root name for the marker cluster images.
1859
+ *
1860
+ * @type {string}
1861
+ * @constant
1862
+ *
1863
+ * MarkerClusterer.IMAGE_PATH = "../images/m";
1864
+ */
1865
+ MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer" + "images/m";
1866
+
1867
+
1868
+ /**
1869
+ * The default extension name for the marker cluster images.
1870
+ *
1871
+ * @type {string}
1872
+ * @constant
1873
+ */
1874
+ MarkerClusterer.IMAGE_EXTENSION = "png";
1875
+
1876
+
1877
+ /**
1878
+ * The default array of sizes for the marker cluster images.
1879
+ *
1880
+ * @type {Array.<number>}
1881
+ * @constant
1882
+ */
1883
+ MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90];