PluginProbe ʕ •ᴥ•ʔ
Premium Addons for Elementor – Powerful Elementor Templates & Widgets / 4.10.78
Premium Addons for Elementor – Powerful Elementor Templates & Widgets v4.10.78
4.11.84 4.11.83 4.11.82 4.11.80 4.11.81 4.11.79 4.11.78 4.11.77 4.11.76 4.11.75 3.20.5 4.11.69 3.20.6 4.11.7 3.20.7 4.11.70 3.20.8 4.11.71 3.20.9 4.11.72 3.21.1 4.11.73 3.21.2 4.11.74 3.21.3 4.11.8 3.21.4 4.11.9 3.21.5 4.2.0 3.21.6 4.2.1 3.3.0 4.2.2 3.3.1 4.2.3 3.3.2 4.2.4 3.3.3 4.2.5 3.3.4 4.2.6 3.3.5 4.2.7 3.3.6 4.2.8 3.3.7 4.2.9 3.3.8 4.3.0 3.3.9 4.3.1 3.4.0 4.3.2 3.4.1 4.3.3 3.4.2 4.3.4 3.4.3 4.3.5 3.4.4 4.3.6 3.4.5 4.3.7 3.4.6 4.3.8 3.4.7 4.3.9 3.4.8 4.4.0 3.4.9 4.4.1 3.5.0 4.4.2 3.5.1 4.4.3 3.5.2 4.4.4 3.5.3 4.4.5 3.5.4 4.4.6 3.5.5 4.4.7 3.5.6 4.4.8 3.5.7 4.4.9 3.5.8 4.5.0 3.5.9 4.5.1 3.6.0 4.5.2 3.6.1 4.5.3 3.6.2 4.5.4 3.6.3 4.5.5 3.6.4 4.5.6 3.6.5 4.5.7 3.6.6 4.5.8 3.6.7 4.5.9 3.6.8 4.6.0 3.6.9 4.6.1 3.7.0 4.7.0 3.7.1 4.7.1 3.7.2 4.7.2 3.7.3 4.7.3 3.7.4 4.7.4 3.7.5 4.7.5 3.7.6 4.7.6 3.7.7 4.7.7 3.7.8 4.7.8 3.7.9 4.7.9 3.8.0 4.8.0 3.8.1 4.8.1 3.8.2 4.8.10 3.8.3 4.8.11 3.8.4 4.8.2 3.8.5 4.8.3 3.8.6 4.8.4 3.8.7 4.8.5 3.8.8 4.8.6 3.8.9 4.8.7 3.9.0 4.8.8 3.9.1 4.8.9 3.9.2 4.9.0 3.9.3 4.9.0-beta1 3.9.4 4.9.0-beta2 3.9.5 4.9.1 3.9.6 4.9.10 3.9.7 4.9.11 3.9.8 4.9.12 3.9.9 4.9.13 4.0.1 4.9.14 4.0.3 4.9.15 4.0.4 4.9.16 4.0.5 4.9.17 4.0.6 4.9.18 4.0.7 4.9.19 4.0.8 4.9.2 4.0.9 4.9.20 4.1.0 4.9.21 4.1.1 4.9.22 4.1.2 4.9.23 4.1.3 4.9.24 trunk 4.1.4 4.9.25 1.0 4.1.5 4.9.26 1.01 4.1.6 4.9.27 1.02 4.1.7 4.9.28 1.03 4.1.8 4.9.29 1.04 4.1.9 4.9.3 1.05 4.10.0 4.9.30 1.06 4.10.1 4.9.31 1.07 4.10.10 4.9.32 1.08 4.10.11 4.9.33 1.09 4.10.12 4.9.34 2.0 4.10.13 4.9.35 2.0.1 4.10.14 4.9.36 2.0.2 4.10.15 4.9.37 2.0.3 4.10.16 4.9.38 2.0.4 4.10.17 4.9.39 2.0.5 4.10.18 4.9.4 2.0.6 4.10.19 4.9.40 2.0.7 4.10.2 4.9.41 2.0.8 4.10.20 4.9.42 2.0.9 4.10.21 4.9.43 2.1.0 4.10.22 4.9.45 2.1.1 4.10.23 4.9.46 2.1.2 4.10.24 4.9.47 2.1.3 4.10.25 4.9.48 2.1.4 4.10.26 4.9.49 2.1.5 4.10.27 4.9.5 2.1.5-beta1 4.10.28 4.9.50 2.1.6 4.10.29 4.9.51 2.1.7 4.10.3 4.9.52 2.1.8 4.10.30 4.9.53 2.1.9 4.10.31 4.9.54 2.2.0 4.10.32 4.9.55 2.2.1 4.10.33 4.9.56 2.2.2 4.10.34 4.9.57 2.2.3 4.10.35 4.9.6 2.2.4 4.10.36 4.9.7 2.2.5 4.10.37 4.9.8 2.2.6 4.10.38 4.9.9 2.2.7 4.10.39 2.2.8 4.10.4 2.2.9 4.10.40 2.3.0 4.10.41 2.3.1 4.10.42 2.3.2 4.10.43 2.3.3 4.10.44 2.3.4 4.10.45 2.3.5 4.10.46 2.3.6 4.10.47 2.3.7 4.10.48 2.3.8 4.10.49 2.3.9 4.10.5 2.4.0 4.10.50 2.4.1 4.10.51 2.5.0 4.10.52 2.5.1 4.10.53 2.5.2 4.10.54 2.5.3 4.10.55 2.5.4 4.10.56 2.5.5 4.10.57 2.5.6 4.10.58 2.5.7 4.10.59 2.5.8 4.10.6 2.5.9 4.10.60 2.6.0 4.10.61 2.6.1 4.10.62 2.6.2 4.10.63 2.6.3 4.10.64 2.6.4 4.10.65 2.6.5 4.10.66 2.6.6 4.10.67 2.6.7 4.10.68 2.6.8 4.10.69 2.6.9 4.10.7 2.7.0 4.10.70 2.7.1 4.10.71 2.7.2 4.10.72 2.7.3 4.10.73 2.7.4 4.10.74 2.7.5 4.10.75 2.7.6 4.10.76 2.7.7 4.10.77 2.7.8 4.10.78 2.7.9 4.10.79 2.8.0 4.10.8 2.8.1 4.10.80 2.8.2 4.10.81 2.8.3 4.10.82 2.8.4 4.10.83 2.8.5 4.10.84 2.8.6 4.10.85 2.8.7 4.10.86 2.8.8 4.10.87 2.8.9 4.10.88 2.9.0 4.10.89 2.9.1 4.10.9 2.9.2 4.10.90 2.9.3 4.11.0 2.9.4 4.11.1 2.9.5 4.11.10 2.9.6 4.11.11 2.9.7 4.11.12 2.9.8 4.11.13 2.9.9 4.11.14 3.0.0 4.11.15 3.0.1 4.11.16 3.0.2 4.11.17 3.0.3 4.11.18 3.0.4 4.11.19 3.0.5 4.11.2 3.0.6 4.11.20 3.0.7 4.11.21 3.0.8 4.11.22 3.0.9 4.11.23 3.1.0 4.11.24 3.1.1 4.11.25 3.1.2 4.11.26 3.1.3 4.11.27 3.1.4 4.11.28 3.1.5 4.11.29 3.1.6 4.11.3 3.1.7 4.11.30 3.1.8 4.11.31 3.1.9 4.11.32 3.10.0 4.11.33 3.10.1 4.11.34 3.10.2 4.11.35 3.10.3 4.11.36 3.10.4 4.11.37 3.10.5 4.11.38 3.10.6 4.11.39 3.10.7 4.11.4 3.10.8 4.11.40 3.10.9 4.11.41 3.11.0 4.11.42 3.11.1 4.11.43 3.11.2 4.11.44 3.11.3 4.11.45 3.11.4 4.11.46 3.11.5 4.11.47 3.11.6 4.11.48 3.11.7 4.11.49 3.11.8 4.11.5 3.11.9 4.11.50 3.12.0 4.11.51 3.12.1 4.11.52 3.12.2 4.11.53 3.12.3 4.11.54 3.2.0 4.11.55 3.2.1 4.11.56 3.2.2 4.11.57 3.2.3 4.11.58 3.2.4 4.11.59 3.2.5 4.11.6 3.2.6 4.11.60 3.2.7 4.11.61 3.2.8 4.11.62 3.2.9 4.11.63 3.20.0 4.11.64 3.20.1 4.11.65 3.20.2 4.11.66 3.20.3 4.11.67 3.20.4 4.11.68
premium-addons-for-elementor / assets / frontend / js / markerclusterer.js
premium-addons-for-elementor / assets / frontend / js Last commit date
TweenMax.js 1 year ago anime.js 1 year ago flipster.js 1 year ago headroom.js 1 year ago iscroll.js 1 year ago isotope.js 1 year ago jquery-countdown.js 1 year ago jquery-mousewheel.js 1 year ago jquery-slimscroll.js 1 year ago lottie.js 1 year ago luxon.js 1 year ago markerclusterer.js 1 year ago modal.js 1 year ago motionpath.js 1 year ago pa-gsap.js 1 year ago pa-scrolldir.js 1 year ago premium-addons.js 1 year ago premium-banner.js 1 year ago premium-blog.js 1 year ago premium-button.js 1 year ago premium-carousel-widget.js 1 year ago premium-contact-form.js 1 year ago premium-countdown-timer.js 1 year ago premium-counter.js 1 year ago premium-dis-conditions.js 1 year ago premium-dual-header.js 1 year ago premium-eq-height.js 1 year ago premium-fancy-text.js 1 year ago premium-floating-effects.js 1 year ago premium-global-tooltips.js 1 year ago premium-icon-list.js 1 year ago premium-image-button.js 1 year ago premium-image-scroll.js 1 year ago premium-img-gallery.js 1 year ago premium-maps.js 1 year ago premium-media-wheel.js 1 year ago premium-mini-cart.js 1 year ago premium-mobile-menu.js 1 year ago premium-modal-box.js 1 year ago premium-nav-menu.js 1 year ago premium-notifications.js 1 year ago premium-person.js 1 year ago premium-pinterest-feed.js 1 year ago premium-post-ticker.js 1 year ago premium-progressbar.js 1 year ago premium-search-form.js 1 year ago premium-shape-divider.js 1 year ago premium-svg-drawer.js 1 year ago premium-tcloud.js 1 year ago premium-testimonials.js 1 year ago premium-textual-showcase.js 1 year ago premium-tiktok-feed.js 1 year ago premium-title.js 1 year ago premium-video-box.js 1 year ago premium-vscroll.js 1 year ago premium-weather.js 1 year ago premium-woo-categories.js 1 year ago premium-woo-cta.js 1 year ago premium-woo-products.js 1 year ago premium-world-clock.js 1 year ago premium-wrapper-link.js 1 year ago prettyPhoto.js 1 year ago scrollTrigger.js 1 year ago slick.js 1 year ago tooltipster.js 1 year ago typed.js 1 year ago universal-tilt.js 1 year ago vticker.js 1 year ago
markerclusterer.js
1248 lines
1 /**
2 * @name MarkerClusterer for Google Maps v3
3 * @version version 1.0.1
4 * @author Luke Mahe
5 * @fileoverview
6 * The library creates and manages per-zoom-level clusters for large amounts of
7 * markers.
8 * <br/>
9 * This is a v3 implementation of the
10 * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
11 * >v2 MarkerClusterer</a>.
12 */
13
14 /**
15 * Licensed under the Apache License, Version 2.0 (the "License");
16 * you may not use this file except in compliance with the License.
17 * You may obtain a copy of the License at
18 *
19 * http://www.apache.org/licenses/LICENSE-2.0
20 *
21 * Unless required by applicable law or agreed to in writing, software
22 * distributed under the License is distributed on an "AS IS" BASIS,
23 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 * See the License for the specific language governing permissions and
25 * limitations under the License.
26 */
27
28
29 /**
30 * A Marker Clusterer that clusters markers.
31 *
32 * @param {google.maps.Map} map The Google map to attach to.
33 * @param {Array.<google.maps.Marker>=} opt_markers Optional markers to add to
34 * the cluster.
35 * @param {Object=} opt_options support the following options:
36 * 'gridSize': (number) The grid size of a cluster in pixels.
37 * 'maxZoom': (number) The maximum zoom level that a marker can be part of a
38 * cluster.
39 * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a
40 * cluster is to zoom into it.
41 * 'averageCenter': (boolean) Whether the center of each cluster should be
42 * the average of all markers in the cluster.
43 * 'minimumClusterSize': (number) The minimum number of markers to be in a
44 * cluster before the markers are hidden and a count
45 * is shown.
46 * 'styles': (object) An object that has style properties:
47 * 'url': (string) The image url.
48 * 'height': (number) The image height.
49 * 'width': (number) The image width.
50 * 'anchor': (Array) The anchor position of the label text.
51 * 'textColor': (string) The text color.
52 * 'textSize': (number) The text size.
53 * 'backgroundPosition': (string) The position of the backgound x, y.
54 * @constructor
55 * @extends google.maps.OverlayView
56 */
57 function MarkerClusterer(map, opt_markers, opt_options) {
58 // MarkerClusterer implements google.maps.OverlayView interface. We use the
59 // extend function to extend MarkerClusterer with google.maps.OverlayView
60 // because it might not always be available when the code is defined so we
61 // look for it at the last possible moment. If it doesn't exist now then
62 // there is no point going ahead :)
63 this.extend(MarkerClusterer, google.maps.OverlayView);
64 this.map_ = map;
65
66 /**
67 * @type {Array.<google.maps.Marker>}
68 * @private
69 */
70 this.markers_ = [];
71
72 /**
73 * @type {Array.<Cluster>}
74 */
75 this.clusters_ = [];
76
77 this.sizes = [53, 56, 66, 78, 90];
78
79 /**
80 * @private
81 */
82 this.styles_ = [];
83
84 /**
85 * @type {boolean}
86 * @private
87 */
88 this.ready_ = false;
89
90 var options = opt_options || {};
91
92 /**
93 * @type {number}
94 * @private
95 */
96 this.gridSize_ = options['gridSize'] || 60;
97
98 /**
99 * @private
100 */
101 this.minClusterSize_ = options['minimumClusterSize'] || 2;
102
103
104 /**
105 * @type {?number}
106 * @private
107 */
108 this.maxZoom_ = options['maxZoom'] || null;
109
110 this.styles_ = options['styles'] || [];
111
112 /**
113 * @type {string}
114 * @private
115 */
116 this.imagePath_ = options['imagePath'] ||
117 this.MARKER_CLUSTER_IMAGE_PATH_;
118
119 /**
120 * @type {string}
121 * @private
122 */
123 this.imageExtension_ = options['imageExtension'] ||
124 this.MARKER_CLUSTER_IMAGE_EXTENSION_;
125
126 /**
127 * @type {boolean}
128 * @private
129 */
130 this.zoomOnClick_ = true;
131
132 if (options['zoomOnClick'] != undefined) {
133 this.zoomOnClick_ = options['zoomOnClick'];
134 }
135
136 /**
137 * @type {boolean}
138 * @private
139 */
140 this.averageCenter_ = false;
141
142 if (options['averageCenter'] != undefined) {
143 this.averageCenter_ = options['averageCenter'];
144 }
145
146 this.setupStyles_();
147
148 this.setMap(map);
149
150 /**
151 * @type {number}
152 * @private
153 */
154 this.prevZoom_ = this.map_.getZoom();
155
156 // Add the map event listeners
157 var that = this;
158 google.maps.event.addListener(this.map_, 'zoom_changed', function () {
159 // Determines map type and prevent illegal zoom levels
160 var zoom = that.map_.getZoom();
161 var minZoom = that.map_.minZoom || 0;
162 var maxZoom = Math.min(that.map_.maxZoom || 100,
163 that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom);
164 zoom = Math.min(Math.max(zoom, minZoom), maxZoom);
165
166 if (that.prevZoom_ != zoom) {
167 that.prevZoom_ = zoom;
168 that.resetViewport();
169 }
170 });
171
172 google.maps.event.addListener(this.map_, 'idle', function () {
173 that.redraw();
174 });
175
176 // Finally, add the markers
177 if (opt_markers && (opt_markers.length || Object.keys(opt_markers).length)) {
178 this.addMarkers(opt_markers, false);
179 }
180 }
181
182
183 /**
184 * The marker cluster image path.
185 *
186 * @type {string}
187 * @private
188 */
189 MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = '../images/m';
190
191
192 /**
193 * The marker cluster image path.
194 *
195 * @type {string}
196 * @private
197 */
198 MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png';
199
200
201 /**
202 * Extends a objects prototype by anothers.
203 *
204 * @param {Object} obj1 The object to be extended.
205 * @param {Object} obj2 The object to extend with.
206 * @return {Object} The new extended object.
207 * @ignore
208 */
209 MarkerClusterer.prototype.extend = function (obj1, obj2) {
210 return (function (object) {
211 for (var property in object.prototype) {
212 this.prototype[property] = object.prototype[property];
213 }
214 return this;
215 }).apply(obj1, [obj2]);
216 };
217
218
219 /**
220 * Implementaion of the interface method.
221 * @ignore
222 */
223 MarkerClusterer.prototype.onAdd = function () {
224 this.setReady_(true);
225 };
226
227 /**
228 * Implementaion of the interface method.
229 * @ignore
230 */
231 MarkerClusterer.prototype.draw = function () { };
232
233 /**
234 * Sets up the styles object.
235 *
236 * @private
237 */
238 MarkerClusterer.prototype.setupStyles_ = function () {
239 if (this.styles_.length) {
240 return;
241 }
242
243 for (var i = 0, size; size = this.sizes[i]; i++) {
244
245 var clusterIcon = this.imagePath_ + (-1 != this.imagePath_.indexOf('developers.google') ? (i + 1) + '.' + this.imageExtension_ : '')
246
247 this.styles_.push({
248 url: clusterIcon,
249 height: size,
250 width: size
251 });
252 }
253
254 };
255
256 /**
257 * Fit the map to the bounds of the markers in the clusterer.
258 */
259 MarkerClusterer.prototype.fitMapToMarkers = function () {
260 var markers = this.getMarkers();
261 var bounds = new google.maps.LatLngBounds();
262 for (var i = 0, marker; marker = markers[i]; i++) {
263 bounds.extend(marker.getPosition());
264 }
265
266 this.map_.fitBounds(bounds);
267 };
268
269
270 /**
271 * Sets the styles.
272 *
273 * @param {Object} styles The style to set.
274 */
275 MarkerClusterer.prototype.setStyles = function (styles) {
276 this.styles_ = styles;
277 };
278
279
280 /**
281 * Gets the styles.
282 *
283 * @return {Object} The styles object.
284 */
285 MarkerClusterer.prototype.getStyles = function () {
286 return this.styles_;
287 };
288
289
290 /**
291 * Whether zoom on click is set.
292 *
293 * @return {boolean} True if zoomOnClick_ is set.
294 */
295 MarkerClusterer.prototype.isZoomOnClick = function () {
296 return this.zoomOnClick_;
297 };
298
299 /**
300 * Whether average center is set.
301 *
302 * @return {boolean} True if averageCenter_ is set.
303 */
304 MarkerClusterer.prototype.isAverageCenter = function () {
305 return this.averageCenter_;
306 };
307
308
309 /**
310 * Returns the array of markers in the clusterer.
311 *
312 * @return {Array.<google.maps.Marker>} The markers.
313 */
314 MarkerClusterer.prototype.getMarkers = function () {
315 return this.markers_;
316 };
317
318
319 /**
320 * Returns the number of markers in the clusterer
321 *
322 * @return {Number} The number of markers.
323 */
324 MarkerClusterer.prototype.getTotalMarkers = function () {
325 return this.markers_.length;
326 };
327
328
329 /**
330 * Sets the max zoom for the clusterer.
331 *
332 * @param {number} maxZoom The max zoom level.
333 */
334 MarkerClusterer.prototype.setMaxZoom = function (maxZoom) {
335 this.maxZoom_ = maxZoom;
336 };
337
338
339 /**
340 * Gets the max zoom for the clusterer.
341 *
342 * @return {number} The max zoom level.
343 */
344 MarkerClusterer.prototype.getMaxZoom = function () {
345 return this.maxZoom_;
346 };
347
348
349 /**
350 * The function for calculating the cluster icon image.
351 *
352 * @param {Array.<google.maps.Marker>} markers The markers in the clusterer.
353 * @param {number} numStyles The number of styles available.
354 * @return {Object} A object properties: 'text' (string) and 'index' (number).
355 * @private
356 */
357 MarkerClusterer.prototype.calculator_ = function (markers, numStyles) {
358 var index = 0;
359 var count = markers.length;
360 var dv = count;
361 while (dv !== 0) {
362 dv = parseInt(dv / 10, 10);
363 index++;
364 }
365
366 index = Math.min(index, numStyles);
367 return {
368 text: count,
369 index: index
370 };
371 };
372
373
374 /**
375 * Set the calculator function.
376 *
377 * @param {function(Array, number)} calculator The function to set as the
378 * calculator. The function should return a object properties:
379 * 'text' (string) and 'index' (number).
380 *
381 */
382 MarkerClusterer.prototype.setCalculator = function (calculator) {
383 this.calculator_ = calculator;
384 };
385
386
387 /**
388 * Get the calculator function.
389 *
390 * @return {function(Array, number)} the calculator function.
391 */
392 MarkerClusterer.prototype.getCalculator = function () {
393 return this.calculator_;
394 };
395
396
397 /**
398 * Add an array of markers to the clusterer.
399 *
400 * @param {Array.<google.maps.Marker>} markers The markers to add.
401 * @param {boolean=} opt_nodraw Whether to redraw the clusters.
402 */
403 MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) {
404 if (markers.length) {
405 for (var i = 0, marker; marker = markers[i]; i++) {
406 this.pushMarkerTo_(marker);
407 }
408 } else if (Object.keys(markers).length) {
409 for (var marker in markers) {
410 this.pushMarkerTo_(markers[marker]);
411 }
412 }
413 if (!opt_nodraw) {
414 this.redraw();
415 }
416 };
417
418
419 /**
420 * Pushes a marker to the clusterer.
421 *
422 * @param {google.maps.Marker} marker The marker to add.
423 * @private
424 */
425 MarkerClusterer.prototype.pushMarkerTo_ = function (marker) {
426 marker.isAdded = false;
427 if (marker['draggable']) {
428 // If the marker is draggable add a listener so we update the clusters on
429 // the drag end.
430 var that = this;
431 google.maps.event.addListener(marker, 'dragend', function () {
432 marker.isAdded = false;
433 that.repaint();
434 });
435 }
436 this.markers_.push(marker);
437 };
438
439
440 /**
441 * Adds a marker to the clusterer and redraws if needed.
442 *
443 * @param {google.maps.Marker} marker The marker to add.
444 * @param {boolean=} opt_nodraw Whether to redraw the clusters.
445 */
446 MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) {
447 this.pushMarkerTo_(marker);
448 if (!opt_nodraw) {
449 this.redraw();
450 }
451 };
452
453
454 /**
455 * Removes a marker and returns true if removed, false if not
456 *
457 * @param {google.maps.Marker} marker The marker to remove
458 * @return {boolean} Whether the marker was removed or not
459 * @private
460 */
461 MarkerClusterer.prototype.removeMarker_ = function (marker) {
462 var index = -1;
463 if (this.markers_.indexOf) {
464 index = this.markers_.indexOf(marker);
465 } else {
466 for (var i = 0, m; m = this.markers_[i]; i++) {
467 if (m == marker) {
468 index = i;
469 break;
470 }
471 }
472 }
473
474 if (index == -1) {
475 // Marker is not in our list of markers.
476 return false;
477 }
478
479 marker.setMap(null);
480
481 this.markers_.splice(index, 1);
482
483 return true;
484 };
485
486
487 /**
488 * Remove a marker from the cluster.
489 *
490 * @param {google.maps.Marker} marker The marker to remove.
491 * @param {boolean=} opt_nodraw Optional boolean to force no redraw.
492 * @return {boolean} True if the marker was removed.
493 */
494 MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) {
495 var removed = this.removeMarker_(marker);
496
497 if (!opt_nodraw && removed) {
498 this.resetViewport();
499 this.redraw();
500 return true;
501 } else {
502 return false;
503 }
504 };
505
506
507 /**
508 * Removes an array of markers from the cluster.
509 *
510 * @param {Array.<google.maps.Marker>} markers The markers to remove.
511 * @param {boolean=} opt_nodraw Optional boolean to force no redraw.
512 */
513 MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) {
514 var removed = false;
515
516 for (var i = 0, marker; marker = markers[i]; i++) {
517 var r = this.removeMarker_(marker);
518 removed = removed || r;
519 }
520
521 if (!opt_nodraw && removed) {
522 this.resetViewport();
523 this.redraw();
524 return true;
525 }
526 };
527
528
529 /**
530 * Sets the clusterer's ready state.
531 *
532 * @param {boolean} ready The state.
533 * @private
534 */
535 MarkerClusterer.prototype.setReady_ = function (ready) {
536 if (!this.ready_) {
537 this.ready_ = ready;
538 this.createClusters_();
539 }
540 };
541
542
543 /**
544 * Returns the number of clusters in the clusterer.
545 *
546 * @return {number} The number of clusters.
547 */
548 MarkerClusterer.prototype.getTotalClusters = function () {
549 return this.clusters_.length;
550 };
551
552
553 /**
554 * Returns the google map that the clusterer is associated with.
555 *
556 * @return {google.maps.Map} The map.
557 */
558 MarkerClusterer.prototype.getMap = function () {
559 return this.map_;
560 };
561
562
563 /**
564 * Sets the google map that the clusterer is associated with.
565 *
566 * @param {google.maps.Map} map The map.
567 */
568 MarkerClusterer.prototype.setMap = function (map) {
569 this.map_ = map;
570 };
571
572
573 /**
574 * Returns the size of the grid.
575 *
576 * @return {number} The grid size.
577 */
578 MarkerClusterer.prototype.getGridSize = function () {
579 return this.gridSize_;
580 };
581
582
583 /**
584 * Sets the size of the grid.
585 *
586 * @param {number} size The grid size.
587 */
588 MarkerClusterer.prototype.setGridSize = function (size) {
589 this.gridSize_ = size;
590 };
591
592
593 /**
594 * Returns the min cluster size.
595 *
596 * @return {number} The grid size.
597 */
598 MarkerClusterer.prototype.getMinClusterSize = function () {
599 return this.minClusterSize_;
600 };
601
602 /**
603 * Sets the min cluster size.
604 *
605 * @param {number} size The grid size.
606 */
607 MarkerClusterer.prototype.setMinClusterSize = function (size) {
608 this.minClusterSize_ = size;
609 };
610
611
612 /**
613 * Extends a bounds object by the grid size.
614 *
615 * @param {google.maps.LatLngBounds} bounds The bounds to extend.
616 * @return {google.maps.LatLngBounds} The extended bounds.
617 */
618 MarkerClusterer.prototype.getExtendedBounds = function (bounds) {
619 var projection = this.getProjection();
620
621 // Turn the bounds into latlng.
622 var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
623 bounds.getNorthEast().lng());
624 var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
625 bounds.getSouthWest().lng());
626
627 // Convert the points to pixels and the extend out by the grid size.
628 var trPix = projection.fromLatLngToDivPixel(tr);
629 trPix.x += this.gridSize_;
630 trPix.y -= this.gridSize_;
631
632 var blPix = projection.fromLatLngToDivPixel(bl);
633 blPix.x -= this.gridSize_;
634 blPix.y += this.gridSize_;
635
636 // Convert the pixel points back to LatLng
637 var ne = projection.fromDivPixelToLatLng(trPix);
638 var sw = projection.fromDivPixelToLatLng(blPix);
639
640 // Extend the bounds to contain the new bounds.
641 bounds.extend(ne);
642 bounds.extend(sw);
643
644 return bounds;
645 };
646
647
648 /**
649 * Determins if a marker is contained in a bounds.
650 *
651 * @param {google.maps.Marker} marker The marker to check.
652 * @param {google.maps.LatLngBounds} bounds The bounds to check against.
653 * @return {boolean} True if the marker is in the bounds.
654 * @private
655 */
656 MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) {
657 return bounds.contains(marker.getPosition());
658 };
659
660
661 /**
662 * Clears all clusters and markers from the clusterer.
663 */
664 MarkerClusterer.prototype.clearMarkers = function () {
665 this.resetViewport(true);
666
667 // Set the markers a empty array.
668 this.markers_ = [];
669 };
670
671
672 /**
673 * Clears all existing clusters and recreates them.
674 * @param {boolean} opt_hide To also hide the marker.
675 */
676 MarkerClusterer.prototype.resetViewport = function (opt_hide) {
677 // Remove all the clusters
678 for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
679 cluster.remove();
680 }
681
682 // Reset the markers to not be added and to be invisible.
683 for (var i = 0, marker; marker = this.markers_[i]; i++) {
684 marker.isAdded = false;
685 if (opt_hide) {
686 marker.setMap(null);
687 }
688 }
689
690 this.clusters_ = [];
691 };
692
693 /**
694 *
695 */
696 MarkerClusterer.prototype.repaint = function () {
697 var oldClusters = this.clusters_.slice();
698 this.clusters_.length = 0;
699 this.resetViewport();
700 this.redraw();
701
702 // Remove the old clusters.
703 // Do it in a timeout so the other clusters have been drawn first.
704 window.setTimeout(function () {
705 for (var i = 0, cluster; cluster = oldClusters[i]; i++) {
706 cluster.remove();
707 }
708 }, 0);
709 };
710
711
712 /**
713 * Redraws the clusters.
714 */
715 MarkerClusterer.prototype.redraw = function () {
716 this.createClusters_();
717 };
718
719
720 /**
721 * Calculates the distance between two latlng locations in km.
722 * @see http://www.movable-type.co.uk/scripts/latlong.html
723 *
724 * @param {google.maps.LatLng} p1 The first lat lng point.
725 * @param {google.maps.LatLng} p2 The second lat lng point.
726 * @return {number} The distance between the two points in km.
727 * @private
728 */
729 MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) {
730 if (!p1 || !p2) {
731 return 0;
732 }
733
734 var R = 6371; // Radius of the Earth in km
735 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
736 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
737 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
738 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
739 Math.sin(dLon / 2) * Math.sin(dLon / 2);
740 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
741 var d = R * c;
742 return d;
743 };
744
745
746 /**
747 * Add a marker to a cluster, or creates a new cluster.
748 *
749 * @param {google.maps.Marker} marker The marker to add.
750 * @private
751 */
752 MarkerClusterer.prototype.addToClosestCluster_ = function (marker) {
753 var distance = 40000; // Some large number
754 var clusterToAddTo = null;
755 var pos = marker.getPosition();
756 for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
757 var center = cluster.getCenter();
758 if (center) {
759 var d = this.distanceBetweenPoints_(center, marker.getPosition());
760 if (d < distance) {
761 distance = d;
762 clusterToAddTo = cluster;
763 }
764 }
765 }
766
767 if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
768 clusterToAddTo.addMarker(marker);
769 } else {
770 var cluster = new Cluster(this);
771 cluster.addMarker(marker);
772 this.clusters_.push(cluster);
773 }
774 };
775
776
777 /**
778 * Creates the clusters.
779 *
780 * @private
781 */
782 MarkerClusterer.prototype.createClusters_ = function () {
783 if (!this.ready_) {
784 return;
785 }
786
787 // Get our current map view bounds.
788 // Create a new bounds object so we don't affect the map.
789 var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(),
790 this.map_.getBounds().getNorthEast());
791 var bounds = this.getExtendedBounds(mapBounds);
792
793 for (var i = 0, marker; marker = this.markers_[i]; i++) {
794 if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
795 this.addToClosestCluster_(marker);
796 }
797 }
798 };
799
800
801 /**
802 * A cluster that contains markers.
803 *
804 * @param {MarkerClusterer} markerClusterer The markerclusterer that this
805 * cluster is associated with.
806 * @constructor
807 * @ignore
808 */
809 function Cluster(markerClusterer) {
810 this.markerClusterer_ = markerClusterer;
811 this.map_ = markerClusterer.getMap();
812 this.gridSize_ = markerClusterer.getGridSize();
813 this.minClusterSize_ = markerClusterer.getMinClusterSize();
814 this.averageCenter_ = markerClusterer.isAverageCenter();
815 this.center_ = null;
816 this.markers_ = [];
817 this.bounds_ = null;
818 this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(),
819 markerClusterer.getGridSize());
820 }
821
822 /**
823 * Determins if a marker is already added to the cluster.
824 *
825 * @param {google.maps.Marker} marker The marker to check.
826 * @return {boolean} True if the marker is already added.
827 */
828 Cluster.prototype.isMarkerAlreadyAdded = function (marker) {
829 if (this.markers_.indexOf) {
830 return this.markers_.indexOf(marker) != -1;
831 } else {
832 for (var i = 0, m; m = this.markers_[i]; i++) {
833 if (m == marker) {
834 return true;
835 }
836 }
837 }
838 return false;
839 };
840
841
842 /**
843 * Add a marker the cluster.
844 *
845 * @param {google.maps.Marker} marker The marker to add.
846 * @return {boolean} True if the marker was added.
847 */
848 Cluster.prototype.addMarker = function (marker) {
849 if (this.isMarkerAlreadyAdded(marker)) {
850 return false;
851 }
852
853 if (!this.center_) {
854 this.center_ = marker.getPosition();
855 this.calculateBounds_();
856 } else {
857 if (this.averageCenter_) {
858 var l = this.markers_.length + 1;
859 var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l;
860 var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l;
861 this.center_ = new google.maps.LatLng(lat, lng);
862 this.calculateBounds_();
863 }
864 }
865
866 marker.isAdded = true;
867 this.markers_.push(marker);
868
869 var len = this.markers_.length;
870 if (len < this.minClusterSize_ && marker.getMap() != this.map_) {
871 // Min cluster size not reached so show the marker.
872 marker.setMap(this.map_);
873 }
874
875 if (len == this.minClusterSize_) {
876 // Hide the markers that were showing.
877 for (var i = 0; i < len; i++) {
878 this.markers_[i].setMap(null);
879 }
880 }
881
882 if (len >= this.minClusterSize_) {
883 marker.setMap(null);
884 }
885
886 this.updateIcon();
887 return true;
888 };
889
890
891 /**
892 * Returns the marker clusterer that the cluster is associated with.
893 *
894 * @return {MarkerClusterer} The associated marker clusterer.
895 */
896 Cluster.prototype.getMarkerClusterer = function () {
897 return this.markerClusterer_;
898 };
899
900
901 /**
902 * Returns the bounds of the cluster.
903 *
904 * @return {google.maps.LatLngBounds} the cluster bounds.
905 */
906 Cluster.prototype.getBounds = function () {
907 var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
908 var markers = this.getMarkers();
909 for (var i = 0, marker; marker = markers[i]; i++) {
910 bounds.extend(marker.getPosition());
911 }
912 return bounds;
913 };
914
915
916 /**
917 * Removes the cluster
918 */
919 Cluster.prototype.remove = function () {
920 this.clusterIcon_.remove();
921 this.markers_.length = 0;
922 delete this.markers_;
923 };
924
925
926 /**
927 * Returns the center of the cluster.
928 *
929 * @return {number} The cluster center.
930 */
931 Cluster.prototype.getSize = function () {
932 return this.markers_.length;
933 };
934
935
936 /**
937 * Returns the center of the cluster.
938 *
939 * @return {Array.<google.maps.Marker>} The cluster center.
940 */
941 Cluster.prototype.getMarkers = function () {
942 return this.markers_;
943 };
944
945
946 /**
947 * Returns the center of the cluster.
948 *
949 * @return {google.maps.LatLng} The cluster center.
950 */
951 Cluster.prototype.getCenter = function () {
952 return this.center_;
953 };
954
955
956 /**
957 * Calculated the extended bounds of the cluster with the grid.
958 *
959 * @private
960 */
961 Cluster.prototype.calculateBounds_ = function () {
962 var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
963 this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
964 };
965
966
967 /**
968 * Determines if a marker lies in the clusters bounds.
969 *
970 * @param {google.maps.Marker} marker The marker to check.
971 * @return {boolean} True if the marker lies in the bounds.
972 */
973 Cluster.prototype.isMarkerInClusterBounds = function (marker) {
974 return this.bounds_.contains(marker.getPosition());
975 };
976
977
978 /**
979 * Returns the map that the cluster is associated with.
980 *
981 * @return {google.maps.Map} The map.
982 */
983 Cluster.prototype.getMap = function () {
984 return this.map_;
985 };
986
987
988 /**
989 * Updates the cluster icon
990 */
991 Cluster.prototype.updateIcon = function () {
992 var zoom = this.map_.getZoom();
993 var mz = this.markerClusterer_.getMaxZoom();
994
995 if (mz && zoom > mz) {
996 // The zoom is greater than our max zoom so show all the markers in cluster.
997 for (var i = 0, marker; marker = this.markers_[i]; i++) {
998 marker.setMap(this.map_);
999 }
1000 return;
1001 }
1002
1003 if (this.markers_.length < this.minClusterSize_) {
1004 // Min cluster size not yet reached.
1005 this.clusterIcon_.hide();
1006 return;
1007 }
1008
1009 var numStyles = this.markerClusterer_.getStyles().length;
1010 var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
1011 this.clusterIcon_.setCenter(this.center_);
1012 this.clusterIcon_.setSums(sums);
1013 this.clusterIcon_.show();
1014 };
1015
1016
1017 /**
1018 * A cluster icon
1019 *
1020 * @param {Cluster} cluster The cluster to be associated with.
1021 * @param {Object} styles An object that has style properties:
1022 * 'url': (string) The image url.
1023 * 'height': (number) The image height.
1024 * 'width': (number) The image width.
1025 * 'anchor': (Array) The anchor position of the label text.
1026 * 'textColor': (string) The text color.
1027 * 'textSize': (number) The text size.
1028 * 'backgroundPosition: (string) The background postition x, y.
1029 * @param {number=} opt_padding Optional padding to apply to the cluster icon.
1030 * @constructor
1031 * @extends google.maps.OverlayView
1032 * @ignore
1033 */
1034 function ClusterIcon(cluster, styles, opt_padding) {
1035 cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
1036
1037 this.styles_ = styles;
1038 this.padding_ = opt_padding || 0;
1039 this.cluster_ = cluster;
1040 this.center_ = null;
1041 this.map_ = cluster.getMap();
1042 this.div_ = null;
1043 this.sums_ = null;
1044 this.visible_ = false;
1045
1046 this.setMap(this.map_);
1047 }
1048
1049
1050 /**
1051 * Triggers the clusterclick event and zoom's if the option is set.
1052 */
1053 ClusterIcon.prototype.triggerClusterClick = function () {
1054 var markerClusterer = this.cluster_.getMarkerClusterer();
1055
1056 // Trigger the clusterclick event.
1057 google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
1058
1059 if (markerClusterer.isZoomOnClick()) {
1060 // Zoom into the cluster.
1061 this.map_.fitBounds(this.cluster_.getBounds());
1062 }
1063 };
1064
1065
1066 /**
1067 * Adding the cluster icon to the dom.
1068 * @ignore
1069 */
1070 ClusterIcon.prototype.onAdd = function () {
1071 this.div_ = document.createElement('DIV');
1072 if (this.visible_) {
1073 var pos = this.getPosFromLatLng_(this.center_);
1074 this.div_.style.cssText = this.createCss(pos);
1075 this.div_.innerHTML = this.sums_.text;
1076 }
1077
1078 var panes = this.getPanes();
1079 panes.overlayMouseTarget.appendChild(this.div_);
1080
1081 var that = this;
1082 google.maps.event.addDomListener(this.div_, 'click', function () {
1083 that.triggerClusterClick();
1084 });
1085 };
1086
1087
1088 /**
1089 * Returns the position to place the div dending on the latlng.
1090 *
1091 * @param {google.maps.LatLng} latlng The position in latlng.
1092 * @return {google.maps.Point} The position in pixels.
1093 * @private
1094 */
1095 ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) {
1096 var pos = this.getProjection().fromLatLngToDivPixel(latlng);
1097 pos.x -= parseInt(this.width_ / 2, 10);
1098 pos.y -= parseInt(this.height_ / 2, 10);
1099 return pos;
1100 };
1101
1102
1103 /**
1104 * Draw the icon.
1105 * @ignore
1106 */
1107 ClusterIcon.prototype.draw = function () {
1108 if (this.visible_) {
1109 var pos = this.getPosFromLatLng_(this.center_);
1110 this.div_.style.top = pos.y + 'px';
1111 this.div_.style.left = pos.x + 'px';
1112 }
1113 };
1114
1115
1116 /**
1117 * Hide the icon.
1118 */
1119 ClusterIcon.prototype.hide = function () {
1120 if (this.div_) {
1121 this.div_.style.display = 'none';
1122 }
1123 this.visible_ = false;
1124 };
1125
1126
1127 /**
1128 * Position and show the icon.
1129 */
1130 ClusterIcon.prototype.show = function () {
1131 if (this.div_) {
1132 var pos = this.getPosFromLatLng_(this.center_);
1133 this.div_.style.cssText = this.createCss(pos);
1134 this.div_.style.display = '';
1135 }
1136 this.visible_ = true;
1137 };
1138
1139
1140 /**
1141 * Remove the icon from the map
1142 */
1143 ClusterIcon.prototype.remove = function () {
1144 this.setMap(null);
1145 };
1146
1147
1148 /**
1149 * Implementation of the onRemove interface.
1150 * @ignore
1151 */
1152 ClusterIcon.prototype.onRemove = function () {
1153 if (this.div_ && this.div_.parentNode) {
1154 this.hide();
1155 this.div_.parentNode.removeChild(this.div_);
1156 this.div_ = null;
1157 }
1158 };
1159
1160
1161 /**
1162 * Set the sums of the icon.
1163 *
1164 * @param {Object} sums The sums containing:
1165 * 'text': (string) The text to display in the icon.
1166 * 'index': (number) The style index of the icon.
1167 */
1168 ClusterIcon.prototype.setSums = function (sums) {
1169 this.sums_ = sums;
1170 this.text_ = sums.text;
1171 this.index_ = sums.index;
1172 if (this.div_) {
1173 this.div_.innerHTML = sums.text;
1174 }
1175
1176 this.useStyle();
1177 };
1178
1179
1180 /**
1181 * Sets the icon to the the styles.
1182 */
1183 ClusterIcon.prototype.useStyle = function () {
1184 var index = Math.max(0, this.sums_.index - 1);
1185 index = Math.min(this.styles_.length - 1, index);
1186 var style = this.styles_[index];
1187 this.url_ = style['url'];
1188 this.height_ = style['height'];
1189 this.width_ = style['width'];
1190 this.textColor_ = style['textColor'];
1191 this.anchor_ = style['anchor'];
1192 this.textSize_ = style['textSize'];
1193 this.backgroundPosition_ = style['backgroundPosition'];
1194 };
1195
1196
1197 /**
1198 * Sets the center of the icon.
1199 *
1200 * @param {google.maps.LatLng} center The latlng to set as the center.
1201 */
1202 ClusterIcon.prototype.setCenter = function (center) {
1203 this.center_ = center;
1204 };
1205
1206
1207 /**
1208 * Create the css text based on the position of the icon.
1209 *
1210 * @param {google.maps.Point} pos The position.
1211 * @return {string} The css style text.
1212 */
1213 ClusterIcon.prototype.createCss = function (pos) {
1214 var style = [];
1215 style.push('background-image:url(' + this.url_ + '); background-size:cover;');
1216 var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0';
1217 style.push('background-position:' + backgroundPosition + ';');
1218
1219 if (typeof this.anchor_ === 'object') {
1220 if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 &&
1221 this.anchor_[0] < this.height_) {
1222 style.push('height:' + (this.height_ - this.anchor_[0]) +
1223 'px; padding-top:' + this.anchor_[0] + 'px;');
1224 } else {
1225 style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
1226 'px;');
1227 }
1228 if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 &&
1229 this.anchor_[1] < this.width_) {
1230 style.push('width:' + (this.width_ - this.anchor_[1]) +
1231 'px; padding-left:' + this.anchor_[1] + 'px;');
1232 } else {
1233 style.push('width:' + this.width_ + 'px; text-align:center;');
1234 }
1235 } else {
1236 style.push('height:' + this.height_ + 'px; line-height:' +
1237 this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
1238 }
1239
1240 var txtColor = this.textColor_ ? this.textColor_ : 'black';
1241 var txtSize = this.textSize_ ? this.textSize_ : 11;
1242
1243 style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
1244 pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' +
1245 txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
1246 return style.join('');
1247 };
1248