├── AUTHORS ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── bower.json ├── docs ├── examples.html └── reference.html ├── examples ├── advanced_example.html ├── data.json ├── simple_example.html ├── speed_test.js └── speed_test_example.html ├── images ├── conv30.png ├── conv40.png ├── conv50.png ├── heart30.png ├── heart40.png ├── heart50.png ├── m1.png ├── m2.png ├── m3.png ├── m4.png ├── m5.png ├── people35.png ├── people45.png ├── people55.png └── pin.png ├── package.json ├── screenshot.png └── src └── markerclusterer.js /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of js-marker-clusterer authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | Google Inc. 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Want to help out? That's awesome! 4 | ![Analytics](https://ga-beacon.appspot.com/UA-12846745-20/js-marker-clusterer/contributing?pixel) 5 | 6 | The library is open source and lives on GitHub at: 7 | https://github.com/googlemaps/js-marker-clusterer 8 | Open an issue or fork the library and submit a pull request. 9 | 10 | Keep in mind that before we can accept any pull requests we have to jump 11 | through a couple of legal hurdles, primarily a Contributor License Agreement 12 | (CLA): 13 | 14 | - **If you are an individual writing original source code** 15 | and you're sure you own the intellectual property, 16 | then you'll need to sign an 17 | [individual CLA](https://developers.google.com/open-source/cla/individual). 18 | - **If you work for a company that wants to allow you to contribute your work**, 19 | then you'll need to sign a 20 | [corporate CLA](https://developers.google.com/open-source/cla/corporate) 21 | 22 | Follow either of the two links above to access the appropriate CLA and 23 | instructions for how to sign and return it. 24 | 25 | When preparing your code, make sure to update the AUTHORS and CONTRIBUTORS file 26 | to reflect your contribtion. 27 | 28 | Once we receive your CLA, we'll be able to review and accept your pull requests. -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | 12 | Luke Mahé 13 | Brendan Kenny 14 | Moisés Arcos 15 | Peter Grassberger 16 | Chris Fritz 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Please note:** This repository is kept for historical purpose only - the current version of marker clusterer is available as part of the [v3-utility-library repository](https://github.com/googlemaps/v3-utility-library/tree/master/markerclustererplus). 2 | 3 | Marker Clusterer – A Google Maps JavaScript API utility library 4 | ============== 5 | 6 | A Google Maps JavaScript API v3 library to create and manage per-zoom-level clusters for large amounts of markers. 7 | ![Analytics](https://maps-ga-beacon.appspot.com/UA-12846745-20/js-marker-clusterer/readme?pixel) 8 | 9 | [Reference documentation](https://googlemaps.github.io/js-marker-clusterer/docs/reference.html) 10 | 11 | Migrated from the [Google Maps JavaScript API utility libraries on Google Code](https://code.google.com/p/google-maps-utility-library-v3/). 12 | 13 | ## Usage 14 | 15 | Download or clone `markerclusterer.js` and images `m1.png` to `m5.png`, save images in `images` folder. 16 | 17 | To use your own custom cluster images just name your images `m[1-5].png` or set the `imagePath` option to the location and name of your images like this: `imagePath: 'customImages/cat'` for images `cat1.png` to `cat5.png`. 18 | 19 | index.html 20 | 21 | ... 22 | 23 |
24 | 25 | 50 | ... 51 | 52 | 53 | ## Live Demos 54 | 55 | [![Marker Clusterer Screenshot](https://googlemaps.github.io/js-marker-clusterer/screenshot.png)](https://googlemaps.github.io/js-marker-clusterer/docs/examples.html) 56 | 57 | [Examples page](https://googlemaps.github.io/js-marker-clusterer/docs/examples.html) 58 | 59 | ## Contributing 60 | 61 | Want to contribute? Check out the [contributing guide](CONTRIBUTING.md)! 62 | 63 | ## License 64 | 65 | Copyright 2014 Google Inc. All rights reserved. 66 | 67 | Licensed under the Apache License, Version 2.0 (the "License"); 68 | you may not use this file except in compliance with the License. 69 | You may obtain a copy of the License at 70 | 71 | http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | Unless required by applicable law or agreed to in writing, software 74 | distributed under the License is distributed on an "AS IS" BASIS, 75 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 76 | See the License for the specific language governing permissions and 77 | limitations under the License. 78 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-marker-clusterer", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/googlemaps/js-marker-clusterer", 5 | "authors": [ 6 | "Luke Mahe" 7 | ], 8 | "description": "The library creates and manages per-zoom-level clusters for large amounts of markers. Google API v3.", 9 | "main": "src/markerclusterer.js", 10 | "keywords": [ 11 | "google", 12 | "marker", 13 | "cluster", 14 | "clusterer", 15 | "javascript", 16 | "js", 17 | "api", 18 | "v3" 19 | ], 20 | "license": "Apache 2.0", 21 | "ignore": [ 22 | "**/.*" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /docs/examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MarkerCluster for v3 Documentation: Examples 6 | 7 | 18 | 19 | 20 | 21 |

22 | MarkerClusterer v3 Examples

23 | 24 |

To use a marker clusterer, create a MarkerClusterer object. 25 | In the simplest case, just pass a map to it.

26 |
 27 | var center = new google.maps.LatLng(37.4419, -122.1419);
 28 | var options = {
 29 |   'zoom': 13,
 30 |   'center': center,
 31 |   'mapTypeId': google.maps.MapTypeId.ROADMAP
 32 | };
 33 | 
 34 | var map = new google.maps.Map(document.getElementById("map"), options);
 35 | var mc = new MarkerClusterer(map);
 36 | 
37 | 38 |

39 | You may also specify a number of options to fine-tune the marker 40 | manager's performance. These options are passed via a object. 41 |

42 |

43 | The imagePath option needs to be set to the location of your local marker images. 44 |

45 | 46 |
 47 | var center = new google.maps.LatLng(37.4419, -122.1419);
 48 | var options = {
 49 |   'zoom': 13,
 50 |   'center': center,
 51 |   'mapTypeId': google.maps.MapTypeId.ROADMAP
 52 | };
 53 | 
 54 | var map = new google.maps.Map(document.getElementById("map"), options);
 55 | 
 56 | var mcOptions = {gridSize: 50, maxZoom: 15, imagePath: 'images/m'};
 57 | var mc = new MarkerClusterer(map, [], mcOptions);
 58 | 
59 | 60 |

Once you create a marker cluster, you will want to add markers to it. 61 | MarkerClusterer supports adding markers using the 62 | addMarker() and addMarkers()method or by 63 | providing a array of markers to the constructor:

64 |
 65 | var center = new google.maps.LatLng(37.4419, -122.1419);
 66 | var options = {
 67 |   'zoom': 13,
 68 |   'center': center,
 69 |   'mapTypeId': google.maps.MapTypeId.ROADMAP
 70 | };
 71 | 
 72 | var map = new google.maps.Map(document.getElementById("map"), options);
 73 | 
 74 | var mcOptions = {gridSize: 50, maxZoom: 15, imagePath: 'images/m'};
 75 | var markers = [...]; // Create the markers you want to add and collect them into a array.
 76 | var mc = new MarkerClusterer(map, markers, mcOptions);
 77 | 
78 | 79 |

A Simple MarkerClusterer Example:

80 | 81 |

This example will show 100 markers on map.

82 | 83 |
 84 | var center = new google.maps.LatLng(37.4419, -122.1419);
 85 | var options = {
 86 |   'zoom': 13,
 87 |   'center': center,
 88 |   'mapTypeId': google.maps.MapTypeId.ROADMAP
 89 | };
 90 | 
 91 | var map = new google.maps.Map(document.getElementById("map"), options);
 92 | 
 93 | var markers = [];
 94 | for (var i = 0; i < 100; i++) {
 95 |   var latLng = new google.maps.LatLng(data.photos[i].latitude,
 96 |       data.photos[i].longitude);
 97 |   var marker = new google.maps.Marker({'position': latLng});
 98 |   markers.push(marker);
 99 | }
100 | var markerCluster = new MarkerClusterer(map, markers, {imagePath: 'images/m'});
101 | 
102 | 103 |

View example (simple_example.html)

104 | 105 |

106 | 107 | Advanced MarkerClusterer Example: 108 |

109 |

With this example, you can test MarkerClusterer with 110 | different max zoom levels, grid size and styles, all the options.

111 |

View example 112 | (advanced_example.html)

113 | 114 |

115 | 116 | Speed Test Example 117 |

118 |

This example will compare adding markers with 119 | MarkerClusterer to the normal method of adding markers, and 120 | display the time for each.

121 |

View example 122 | (speed_test_example.html)

123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/reference.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MarkerClusterer for Google Maps v3 version 1.0 Reference 6 | 82 | 93 | 94 | 95 |

MarkerClusterer for Google Maps v3

96 |

97 | The library creates and manages per-zoom-level clusters for large amounts of 98 | markers. 99 |
100 | This is a v3 implementation of the 101 | v2 MarkerClusterer.

102 |

For a description and examples of how to use this library, check out the how-to.

103 | 104 |

class MarkerClusterer

105 |

This class extends google.maps.OverlayView.

106 | 107 |

Constructor

108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
ConstructorDescription
MarkerClusterer(map:google.maps.Map, opt_markers:Array.<google.maps.Marker>, opt_options:Object)A Marker Clusterer that clusters markers.
125 | 126 |

Options

127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 |
NameTypeDescription
gridSizenumberThe grid size of a cluster in pixels.
maxZoomnumberThe maximum zoom level that a marker can be part of a cluster.
zoomOnClickbooleanWhether the default behaviour of clicking on a cluster is to zoom into it.
averageCenterbooleanWhether the center of each cluster should be the average of all markers in the cluster.
minimumClusterSizenumberThe minimum number of markers to be in a cluster before the markers are hidden and a count is shown.
stylesobjectAn object that has style properties.
170 | 171 |

Styles

172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 |
NameTypeDescription
urlstringThe image url.
heightnumberThe image height.
widthnumberThe image width.
anchorArrayThe anchor position of the label text.
textColorstringThe text color.
textSizenumberThe text size.
backgroundPositionstringThe position of the backgound x, y.
iconAnchorArrayThe anchor position of the icon x, y.
225 | 226 |

Methods

227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 |
MethodsReturn ValueDescription
addMarker(marker:google.maps.Marker, opt_nodraw:boolean)NoneAdds a marker to the clusterer and redraws if needed.
addMarkers(markers:Array.<google.maps.Marker>, opt_nodraw:boolean)NoneAdds an array of markers to the clusterer.
clearMarkers()NoneClears all clusters and markers from the clusterer.
getCalculator()function(Array|number)Gets the calculator function.
getExtendedBounds(bounds:google.maps.LatLngBounds)google.maps.LatLngBoundsExtends a bounds object by the grid size.
getGridSize()numberGets the size of the grid.
getMap()google.maps.MapGets the google map that the clusterer is associated with.
getMarkers()Array.Gets the array of markers in the clusterer.
getMaxZoom()numberGets the max zoom for the clusterer.
getStyles()ObjectGets the styles.
getTotalClusters()numberGets the number of clusters in the clusterer.
getTotalMarkers()Array.Gets the array of markers in the clusterer.
isZoomOnClick()booleanWhether zoom on click is set.
redraw()NoneRedraws the clusters.
removeMarker(marker:google.maps.Marker)booleanRemoves a marker from the cluster.
resetViewport()NoneClears all existing clusters and recreates them.
setCalculator(calculator:function(Array|number))NoneSets the calculator function.
setGridSize(size:number)NoneSets the size of the grid.
setMap(map:google.maps.Map)NoneSets the google map that the clusterer is associated with.
setMaxZoom(maxZoom:number)NoneSets the max zoom for the clusterer.
setStyles(styles:Object)NoneSets the styles.
452 | 453 | 454 | -------------------------------------------------------------------------------- /examples/advanced_example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MarkerClusterer v3 Advanced Example 6 | 7 | 39 | 40 | 41 | 42 | 43 | 180 | 181 | 182 |

An example of MarkerClusterer v3

183 |
184 |
185 |
186 |
187 | Max zoom level: 188 | 199 | 200 | 201 | Cluster size: 202 | 209 | 210 | Cluster style: 211 | 218 | 219 | Clear 220 |
221 | 222 | 223 | -------------------------------------------------------------------------------- /examples/simple_example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MarkerClusterer v3 Simple Example 6 | 28 | 29 | 30 | 31 | 32 | 33 | 57 | 68 | 69 | 70 |

A simple example of MarkerClusterer (100 markers)

71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/speed_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview This demo is used for MarkerClusterer. It will show 100 markers 3 | * using MarkerClusterer and count the time to show the difference between using 4 | * MarkerClusterer and without MarkerClusterer. 5 | * @author Luke Mahe (v2 author: Xiaoxi Wu) 6 | */ 7 | 8 | function $(element) { 9 | return document.getElementById(element); 10 | } 11 | 12 | var speedTest = {}; 13 | 14 | speedTest.pics = null; 15 | speedTest.map = null; 16 | speedTest.markerClusterer = null; 17 | speedTest.markers = []; 18 | speedTest.infoWindow = null; 19 | 20 | speedTest.init = function() { 21 | var latlng = new google.maps.LatLng(39.91, 116.38); 22 | var options = { 23 | 'zoom': 2, 24 | 'center': latlng, 25 | 'mapTypeId': google.maps.MapTypeId.ROADMAP 26 | }; 27 | 28 | speedTest.map = new google.maps.Map($('map'), options); 29 | speedTest.pics = data.photos; 30 | 31 | var useGmm = document.getElementById('usegmm'); 32 | google.maps.event.addDomListener(useGmm, 'click', speedTest.change); 33 | 34 | var numMarkers = document.getElementById('nummarkers'); 35 | google.maps.event.addDomListener(numMarkers, 'change', speedTest.change); 36 | 37 | speedTest.infoWindow = new google.maps.InfoWindow(); 38 | 39 | speedTest.showMarkers(); 40 | }; 41 | 42 | speedTest.showMarkers = function() { 43 | speedTest.markers = []; 44 | 45 | var type = 1; 46 | if ($('usegmm').checked) { 47 | type = 0; 48 | } 49 | 50 | if (speedTest.markerClusterer) { 51 | speedTest.markerClusterer.clearMarkers(); 52 | } 53 | 54 | var panel = $('markerlist'); 55 | panel.innerHTML = ''; 56 | var numMarkers = $('nummarkers').value; 57 | 58 | for (var i = 0; i < numMarkers; i++) { 59 | var titleText = speedTest.pics[i].photo_title; 60 | if (titleText === '') { 61 | titleText = 'No title'; 62 | } 63 | 64 | var item = document.createElement('DIV'); 65 | var title = document.createElement('A'); 66 | title.href = '#'; 67 | title.className = 'title'; 68 | title.innerHTML = titleText; 69 | 70 | item.appendChild(title); 71 | panel.appendChild(item); 72 | 73 | 74 | var latLng = new google.maps.LatLng(speedTest.pics[i].latitude, 75 | speedTest.pics[i].longitude); 76 | 77 | var imageUrl = 'http://chart.apis.google.com/chart?cht=mm&chs=24x32&chco=' + 78 | 'FFFFFF,008CFF,000000&ext=.png'; 79 | var markerImage = new google.maps.MarkerImage(imageUrl, 80 | new google.maps.Size(24, 32)); 81 | 82 | var marker = new google.maps.Marker({ 83 | 'position': latLng, 84 | 'icon': markerImage 85 | }); 86 | 87 | var fn = speedTest.markerClickFunction(speedTest.pics[i], latLng); 88 | google.maps.event.addListener(marker, 'click', fn); 89 | google.maps.event.addDomListener(title, 'click', fn); 90 | speedTest.markers.push(marker); 91 | } 92 | 93 | window.setTimeout(speedTest.time, 0); 94 | }; 95 | 96 | speedTest.markerClickFunction = function(pic, latlng) { 97 | return function(e) { 98 | e.cancelBubble = true; 99 | e.returnValue = false; 100 | if (e.stopPropagation) { 101 | e.stopPropagation(); 102 | e.preventDefault(); 103 | } 104 | var title = pic.photo_title; 105 | var url = pic.photo_url; 106 | var fileurl = pic.photo_file_url; 107 | 108 | var infoHtml = '

' + title + 109 | '

' + 110 | '
' + 112 | '' + 113 | '
' + 115 | '' + pic.owner_name + 116 | '
'; 117 | 118 | speedTest.infoWindow.setContent(infoHtml); 119 | speedTest.infoWindow.setPosition(latlng); 120 | speedTest.infoWindow.open(speedTest.map); 121 | }; 122 | }; 123 | 124 | speedTest.clear = function() { 125 | $('timetaken').innerHTML = 'cleaning...'; 126 | for (var i = 0, marker; marker = speedTest.markers[i]; i++) { 127 | marker.setMap(null); 128 | } 129 | }; 130 | 131 | speedTest.change = function() { 132 | speedTest.clear(); 133 | speedTest.showMarkers(); 134 | }; 135 | 136 | speedTest.time = function() { 137 | $('timetaken').innerHTML = 'timing...'; 138 | var start = new Date(); 139 | if ($('usegmm').checked) { 140 | speedTest.markerClusterer = new MarkerClusterer(speedTest.map, speedTest.markers, {imagePath: '../images/m'}); 141 | } else { 142 | for (var i = 0, marker; marker = speedTest.markers[i]; i++) { 143 | marker.setMap(speedTest.map); 144 | } 145 | } 146 | 147 | var end = new Date(); 148 | $('timetaken').innerHTML = end - start; 149 | }; 150 | -------------------------------------------------------------------------------- /examples/speed_test_example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MarkerClusterer v3 Speed Test Example 6 | 7 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 78 | 89 | 90 | 91 | 92 |
93 |

An example of MarkerClusterer v3

94 | 95 |
96 | 97 | Use MarkerClusterer 98 |
99 | 100 |
101 | Markers: 102 | 109 | 110 | Time used: ms 111 |
112 | 113 | Marker List 114 |
115 | 116 |
117 |
118 |
119 |
120 |
121 | 122 | 123 | -------------------------------------------------------------------------------- /images/conv30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/conv30.png -------------------------------------------------------------------------------- /images/conv40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/conv40.png -------------------------------------------------------------------------------- /images/conv50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/conv50.png -------------------------------------------------------------------------------- /images/heart30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/heart30.png -------------------------------------------------------------------------------- /images/heart40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/heart40.png -------------------------------------------------------------------------------- /images/heart50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/heart50.png -------------------------------------------------------------------------------- /images/m1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/m1.png -------------------------------------------------------------------------------- /images/m2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/m2.png -------------------------------------------------------------------------------- /images/m3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/m3.png -------------------------------------------------------------------------------- /images/m4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/m4.png -------------------------------------------------------------------------------- /images/m5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/m5.png -------------------------------------------------------------------------------- /images/people35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/people35.png -------------------------------------------------------------------------------- /images/people45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/people45.png -------------------------------------------------------------------------------- /images/people55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/people55.png -------------------------------------------------------------------------------- /images/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/images/pin.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-marker-clusterer", 3 | "version": "1.0.0", 4 | "description": "The library creates and manages per-zoom-level clusters for large amounts of markers. Google API v3.", 5 | "main": "src/markerclusterer.js", 6 | "directories": { 7 | "doc": "docs", 8 | "example": "examples" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/googlemaps/js-marker-clusterer.git" 13 | }, 14 | "keywords": [ 15 | "google", 16 | "marker", 17 | "cluster", 18 | "clusterer", 19 | "javascript", 20 | "js", 21 | "api", 22 | "v3" 23 | ], 24 | "author": "Luke Mahe", 25 | "license": "Apache-2.0", 26 | "bugs": { 27 | "url": "https://github.com/googlemaps/js-marker-clusterer/issues" 28 | }, 29 | "homepage": "https://github.com/googlemaps/js-marker-clusterer#readme" 30 | } 31 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/8c2be07696e0c8789a4e314e12fc698622bf8323/screenshot.png -------------------------------------------------------------------------------- /src/markerclusterer.js: -------------------------------------------------------------------------------- 1 | // ==ClosureCompiler== 2 | // @compilation_level ADVANCED_OPTIMIZATIONS 3 | // @externs_url https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/maps/google_maps_api_v3.js 4 | // ==/ClosureCompiler== 5 | 6 | /** 7 | * @name MarkerClusterer for Google Maps v3 8 | * @version version 1.0 9 | * @author Luke Mahe 10 | * @fileoverview 11 | * The library creates and manages per-zoom-level clusters for large amounts of 12 | * markers. 13 | *
14 | * This is a v3 implementation of the 15 | * v2 MarkerClusterer. 17 | */ 18 | 19 | /** 20 | * @license 21 | * Copyright 2010 Google Inc. All Rights Reserved. 22 | * 23 | * Licensed under the Apache License, Version 2.0 (the "License"); 24 | * you may not use this file except in compliance with the License. 25 | * You may obtain a copy of the License at 26 | * 27 | * http://www.apache.org/licenses/LICENSE-2.0 28 | * 29 | * Unless required by applicable law or agreed to in writing, software 30 | * distributed under the License is distributed on an "AS IS" BASIS, 31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | * See the License for the specific language governing permissions and 33 | * limitations under the License. 34 | */ 35 | 36 | 37 | /** 38 | * A Marker Clusterer that clusters markers. 39 | * 40 | * @param {google.maps.Map} map The Google map to attach to. 41 | * @param {Array.=} opt_markers Optional markers to add to 42 | * the cluster. 43 | * @param {Object=} opt_options support the following options: 44 | * 'gridSize': (number) The grid size of a cluster in pixels. 45 | * 'maxZoom': (number) The maximum zoom level that a marker can be part of a 46 | * cluster. 47 | * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a 48 | * cluster is to zoom into it. 49 | * 'averageCenter': (boolean) Whether the center of each cluster should be 50 | * the average of all markers in the cluster. 51 | * 'minimumClusterSize': (number) The minimum number of markers to be in a 52 | * cluster before the markers are hidden and a count 53 | * is shown. 54 | * 'styles': (object) An object that has style properties: 55 | * 'url': (string) The image url. 56 | * 'height': (number) The image height. 57 | * 'width': (number) The image width. 58 | * 'anchor': (Array) The anchor position of the label text. 59 | * 'textColor': (string) The text color. 60 | * 'textSize': (number) The text size. 61 | * 'backgroundPosition': (string) The position of the backgound x, y. 62 | * 'iconAnchor': (Array) The anchor position of the icon x, y. 63 | * @constructor 64 | * @extends google.maps.OverlayView 65 | */ 66 | function MarkerClusterer(map, opt_markers, opt_options) { 67 | // MarkerClusterer implements google.maps.OverlayView interface. We use the 68 | // extend function to extend MarkerClusterer with google.maps.OverlayView 69 | // because it might not always be available when the code is defined so we 70 | // look for it at the last possible moment. If it doesn't exist now then 71 | // there is no point going ahead :) 72 | this.extend(MarkerClusterer, google.maps.OverlayView); 73 | this.map_ = map; 74 | 75 | /** 76 | * @type {Array.} 77 | * @private 78 | */ 79 | this.markers_ = []; 80 | 81 | /** 82 | * @type {Array.} 83 | */ 84 | this.clusters_ = []; 85 | 86 | this.sizes = [53, 56, 66, 78, 90]; 87 | 88 | /** 89 | * @private 90 | */ 91 | this.styles_ = []; 92 | 93 | /** 94 | * @type {boolean} 95 | * @private 96 | */ 97 | this.ready_ = false; 98 | 99 | var options = opt_options || {}; 100 | 101 | /** 102 | * @type {number} 103 | * @private 104 | */ 105 | this.gridSize_ = options['gridSize'] || 60; 106 | 107 | /** 108 | * @private 109 | */ 110 | this.minClusterSize_ = options['minimumClusterSize'] || 2; 111 | 112 | 113 | /** 114 | * @type {?number} 115 | * @private 116 | */ 117 | this.maxZoom_ = options['maxZoom'] || null; 118 | 119 | this.styles_ = options['styles'] || []; 120 | 121 | /** 122 | * @type {string} 123 | * @private 124 | */ 125 | this.imagePath_ = options['imagePath'] || 126 | this.MARKER_CLUSTER_IMAGE_PATH_; 127 | 128 | /** 129 | * @type {string} 130 | * @private 131 | */ 132 | this.imageExtension_ = options['imageExtension'] || 133 | this.MARKER_CLUSTER_IMAGE_EXTENSION_; 134 | 135 | /** 136 | * @type {boolean} 137 | * @private 138 | */ 139 | this.zoomOnClick_ = true; 140 | 141 | if (options['zoomOnClick'] != undefined) { 142 | this.zoomOnClick_ = options['zoomOnClick']; 143 | } 144 | 145 | /** 146 | * @type {boolean} 147 | * @private 148 | */ 149 | this.averageCenter_ = false; 150 | 151 | if (options['averageCenter'] != undefined) { 152 | this.averageCenter_ = options['averageCenter']; 153 | } 154 | 155 | this.setupStyles_(); 156 | 157 | this.setMap(map); 158 | 159 | /** 160 | * @type {number} 161 | * @private 162 | */ 163 | this.prevZoom_ = this.map_.getZoom(); 164 | 165 | // Add the map event listeners 166 | var that = this; 167 | google.maps.event.addListener(this.map_, 'zoom_changed', function() { 168 | var zoom = that.map_.getZoom(); 169 | 170 | if (that.prevZoom_ != zoom) { 171 | that.prevZoom_ = zoom; 172 | that.resetViewport(); 173 | } 174 | }); 175 | 176 | google.maps.event.addListener(this.map_, 'idle', function() { 177 | that.redraw(); 178 | }); 179 | 180 | // Finally, add the markers 181 | if (opt_markers && opt_markers.length) { 182 | this.addMarkers(opt_markers, false); 183 | } 184 | } 185 | 186 | 187 | /** 188 | * The marker cluster image path. 189 | * 190 | * @type {string} 191 | * @private 192 | */ 193 | MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = '../images/m'; 194 | 195 | 196 | /** 197 | * The marker cluster image path. 198 | * 199 | * @type {string} 200 | * @private 201 | */ 202 | MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; 203 | 204 | 205 | /** 206 | * Extends a objects prototype by anothers. 207 | * 208 | * @param {Object} obj1 The object to be extended. 209 | * @param {Object} obj2 The object to extend with. 210 | * @return {Object} The new extended object. 211 | * @ignore 212 | */ 213 | MarkerClusterer.prototype.extend = function(obj1, obj2) { 214 | return (function(object) { 215 | for (var property in object.prototype) { 216 | this.prototype[property] = object.prototype[property]; 217 | } 218 | return this; 219 | }).apply(obj1, [obj2]); 220 | }; 221 | 222 | 223 | /** 224 | * Implementaion of the interface method. 225 | * @ignore 226 | */ 227 | MarkerClusterer.prototype.onAdd = function() { 228 | this.setReady_(true); 229 | }; 230 | 231 | /** 232 | * Implementaion of the interface method. 233 | * @ignore 234 | */ 235 | MarkerClusterer.prototype.draw = function() {}; 236 | 237 | /** 238 | * Sets up the styles object. 239 | * 240 | * @private 241 | */ 242 | MarkerClusterer.prototype.setupStyles_ = function() { 243 | if (this.styles_.length) { 244 | return; 245 | } 246 | 247 | for (var i = 0, size; size = this.sizes[i]; i++) { 248 | this.styles_.push({ 249 | url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_, 250 | height: size, 251 | width: size 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.} 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.} 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.} 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 | for (var i = 0, marker; marker = markers[i]; i++) { 405 | this.pushMarkerTo_(marker); 406 | } 407 | if (!opt_nodraw) { 408 | this.redraw(); 409 | } 410 | }; 411 | 412 | 413 | /** 414 | * Pushes a marker to the clusterer. 415 | * 416 | * @param {google.maps.Marker} marker The marker to add. 417 | * @private 418 | */ 419 | MarkerClusterer.prototype.pushMarkerTo_ = function(marker) { 420 | marker.isAdded = false; 421 | if (marker['draggable']) { 422 | // If the marker is draggable add a listener so we update the clusters on 423 | // the drag end. 424 | var that = this; 425 | google.maps.event.addListener(marker, 'dragend', function() { 426 | marker.isAdded = false; 427 | that.repaint(); 428 | }); 429 | } 430 | this.markers_.push(marker); 431 | }; 432 | 433 | 434 | /** 435 | * Adds a marker to the clusterer and redraws if needed. 436 | * 437 | * @param {google.maps.Marker} marker The marker to add. 438 | * @param {boolean=} opt_nodraw Whether to redraw the clusters. 439 | */ 440 | MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) { 441 | this.pushMarkerTo_(marker); 442 | if (!opt_nodraw) { 443 | this.redraw(); 444 | } 445 | }; 446 | 447 | 448 | /** 449 | * Removes a marker and returns true if removed, false if not 450 | * 451 | * @param {google.maps.Marker} marker The marker to remove 452 | * @return {boolean} Whether the marker was removed or not 453 | * @private 454 | */ 455 | MarkerClusterer.prototype.removeMarker_ = function(marker) { 456 | var index = -1; 457 | if (this.markers_.indexOf) { 458 | index = this.markers_.indexOf(marker); 459 | } else { 460 | for (var i = 0, m; m = this.markers_[i]; i++) { 461 | if (m == marker) { 462 | index = i; 463 | break; 464 | } 465 | } 466 | } 467 | 468 | if (index == -1) { 469 | // Marker is not in our list of markers. 470 | return false; 471 | } 472 | 473 | marker.setMap(null); 474 | 475 | this.markers_.splice(index, 1); 476 | 477 | return true; 478 | }; 479 | 480 | 481 | /** 482 | * Remove a marker from the cluster. 483 | * 484 | * @param {google.maps.Marker} marker The marker to remove. 485 | * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 486 | * @return {boolean} True if the marker was removed. 487 | */ 488 | MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) { 489 | var removed = this.removeMarker_(marker); 490 | 491 | if (!opt_nodraw && removed) { 492 | this.resetViewport(); 493 | this.redraw(); 494 | return true; 495 | } else { 496 | return false; 497 | } 498 | }; 499 | 500 | 501 | /** 502 | * Removes an array of markers from the cluster. 503 | * 504 | * @param {Array.} markers The markers to remove. 505 | * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 506 | */ 507 | MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) { 508 | var removed = false; 509 | 510 | for (var i = 0, marker; marker = markers[i]; i++) { 511 | var r = this.removeMarker_(marker); 512 | removed = removed || r; 513 | } 514 | 515 | if (!opt_nodraw && removed) { 516 | this.resetViewport(); 517 | this.redraw(); 518 | return true; 519 | } 520 | }; 521 | 522 | 523 | /** 524 | * Sets the clusterer's ready state. 525 | * 526 | * @param {boolean} ready The state. 527 | * @private 528 | */ 529 | MarkerClusterer.prototype.setReady_ = function(ready) { 530 | if (!this.ready_) { 531 | this.ready_ = ready; 532 | this.createClusters_(); 533 | } 534 | }; 535 | 536 | 537 | /** 538 | * Returns the number of clusters in the clusterer. 539 | * 540 | * @return {number} The number of clusters. 541 | */ 542 | MarkerClusterer.prototype.getTotalClusters = function() { 543 | return this.clusters_.length; 544 | }; 545 | 546 | 547 | /** 548 | * Returns the google map that the clusterer is associated with. 549 | * 550 | * @return {google.maps.Map} The map. 551 | */ 552 | MarkerClusterer.prototype.getMap = function() { 553 | return this.map_; 554 | }; 555 | 556 | 557 | /** 558 | * Sets the google map that the clusterer is associated with. 559 | * 560 | * @param {google.maps.Map} map The map. 561 | */ 562 | MarkerClusterer.prototype.setMap = function(map) { 563 | this.map_ = map; 564 | }; 565 | 566 | 567 | /** 568 | * Returns the size of the grid. 569 | * 570 | * @return {number} The grid size. 571 | */ 572 | MarkerClusterer.prototype.getGridSize = function() { 573 | return this.gridSize_; 574 | }; 575 | 576 | 577 | /** 578 | * Sets the size of the grid. 579 | * 580 | * @param {number} size The grid size. 581 | */ 582 | MarkerClusterer.prototype.setGridSize = function(size) { 583 | this.gridSize_ = size; 584 | }; 585 | 586 | 587 | /** 588 | * Returns the min cluster size. 589 | * 590 | * @return {number} The grid size. 591 | */ 592 | MarkerClusterer.prototype.getMinClusterSize = function() { 593 | return this.minClusterSize_; 594 | }; 595 | 596 | /** 597 | * Sets the min cluster size. 598 | * 599 | * @param {number} size The grid size. 600 | */ 601 | MarkerClusterer.prototype.setMinClusterSize = function(size) { 602 | this.minClusterSize_ = size; 603 | }; 604 | 605 | 606 | /** 607 | * Extends a bounds object by the grid size. 608 | * 609 | * @param {google.maps.LatLngBounds} bounds The bounds to extend. 610 | * @return {google.maps.LatLngBounds} The extended bounds. 611 | */ 612 | MarkerClusterer.prototype.getExtendedBounds = function(bounds) { 613 | var projection = this.getProjection(); 614 | 615 | // Turn the bounds into latlng. 616 | var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), 617 | bounds.getNorthEast().lng()); 618 | var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), 619 | bounds.getSouthWest().lng()); 620 | 621 | // Convert the points to pixels and the extend out by the grid size. 622 | var trPix = projection.fromLatLngToDivPixel(tr); 623 | trPix.x += this.gridSize_; 624 | trPix.y -= this.gridSize_; 625 | 626 | var blPix = projection.fromLatLngToDivPixel(bl); 627 | blPix.x -= this.gridSize_; 628 | blPix.y += this.gridSize_; 629 | 630 | // Convert the pixel points back to LatLng 631 | var ne = projection.fromDivPixelToLatLng(trPix); 632 | var sw = projection.fromDivPixelToLatLng(blPix); 633 | 634 | // Extend the bounds to contain the new bounds. 635 | bounds.extend(ne); 636 | bounds.extend(sw); 637 | 638 | return bounds; 639 | }; 640 | 641 | 642 | /** 643 | * Determins if a marker is contained in a bounds. 644 | * 645 | * @param {google.maps.Marker} marker The marker to check. 646 | * @param {google.maps.LatLngBounds} bounds The bounds to check against. 647 | * @return {boolean} True if the marker is in the bounds. 648 | * @private 649 | */ 650 | MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) { 651 | return bounds.contains(marker.getPosition()); 652 | }; 653 | 654 | 655 | /** 656 | * Clears all clusters and markers from the clusterer. 657 | */ 658 | MarkerClusterer.prototype.clearMarkers = function() { 659 | this.resetViewport(true); 660 | 661 | // Set the markers a empty array. 662 | this.markers_ = []; 663 | }; 664 | 665 | 666 | /** 667 | * Clears all existing clusters and recreates them. 668 | * @param {boolean} opt_hide To also hide the marker. 669 | */ 670 | MarkerClusterer.prototype.resetViewport = function(opt_hide) { 671 | // Remove all the clusters 672 | for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 673 | cluster.remove(); 674 | } 675 | 676 | // Reset the markers to not be added and to be invisible. 677 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 678 | marker.isAdded = false; 679 | if (opt_hide) { 680 | marker.setMap(null); 681 | } 682 | } 683 | 684 | this.clusters_ = []; 685 | }; 686 | 687 | /** 688 | * 689 | */ 690 | MarkerClusterer.prototype.repaint = function() { 691 | var oldClusters = this.clusters_.slice(); 692 | this.clusters_.length = 0; 693 | this.resetViewport(); 694 | this.redraw(); 695 | 696 | // Remove the old clusters. 697 | // Do it in a timeout so the other clusters have been drawn first. 698 | window.setTimeout(function() { 699 | for (var i = 0, cluster; cluster = oldClusters[i]; i++) { 700 | cluster.remove(); 701 | } 702 | }, 0); 703 | }; 704 | 705 | 706 | /** 707 | * Redraws the clusters. 708 | */ 709 | MarkerClusterer.prototype.redraw = function() { 710 | this.createClusters_(); 711 | }; 712 | 713 | 714 | /** 715 | * Calculates the distance between two latlng locations in km. 716 | * @see http://www.movable-type.co.uk/scripts/latlong.html 717 | * 718 | * @param {google.maps.LatLng} p1 The first lat lng point. 719 | * @param {google.maps.LatLng} p2 The second lat lng point. 720 | * @return {number} The distance between the two points in km. 721 | * @private 722 | */ 723 | MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { 724 | if (!p1 || !p2) { 725 | return 0; 726 | } 727 | 728 | var R = 6371; // Radius of the Earth in km 729 | var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; 730 | var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; 731 | var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 732 | Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * 733 | Math.sin(dLon / 2) * Math.sin(dLon / 2); 734 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 735 | var d = R * c; 736 | return d; 737 | }; 738 | 739 | 740 | /** 741 | * Add a marker to a cluster, or creates a new cluster. 742 | * 743 | * @param {google.maps.Marker} marker The marker to add. 744 | * @private 745 | */ 746 | MarkerClusterer.prototype.addToClosestCluster_ = function(marker) { 747 | var distance = 40000; // Some large number 748 | var clusterToAddTo = null; 749 | var pos = marker.getPosition(); 750 | for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 751 | var center = cluster.getCenter(); 752 | if (center) { 753 | var d = this.distanceBetweenPoints_(center, marker.getPosition()); 754 | if (d < distance) { 755 | distance = d; 756 | clusterToAddTo = cluster; 757 | } 758 | } 759 | } 760 | 761 | if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { 762 | clusterToAddTo.addMarker(marker); 763 | } else { 764 | var cluster = new Cluster(this); 765 | cluster.addMarker(marker); 766 | this.clusters_.push(cluster); 767 | } 768 | }; 769 | 770 | 771 | /** 772 | * Creates the clusters. 773 | * 774 | * @private 775 | */ 776 | MarkerClusterer.prototype.createClusters_ = function() { 777 | if (!this.ready_) { 778 | return; 779 | } 780 | 781 | // Get our current map view bounds. 782 | // Create a new bounds object so we don't affect the map. 783 | var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), 784 | this.map_.getBounds().getNorthEast()); 785 | var bounds = this.getExtendedBounds(mapBounds); 786 | 787 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 788 | if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { 789 | this.addToClosestCluster_(marker); 790 | } 791 | } 792 | }; 793 | 794 | 795 | /** 796 | * A cluster that contains markers. 797 | * 798 | * @param {MarkerClusterer} markerClusterer The markerclusterer that this 799 | * cluster is associated with. 800 | * @constructor 801 | * @ignore 802 | */ 803 | function Cluster(markerClusterer) { 804 | this.markerClusterer_ = markerClusterer; 805 | this.map_ = markerClusterer.getMap(); 806 | this.gridSize_ = markerClusterer.getGridSize(); 807 | this.minClusterSize_ = markerClusterer.getMinClusterSize(); 808 | this.averageCenter_ = markerClusterer.isAverageCenter(); 809 | this.center_ = null; 810 | this.markers_ = []; 811 | this.bounds_ = null; 812 | this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(), 813 | markerClusterer.getGridSize()); 814 | } 815 | 816 | /** 817 | * Determins if a marker is already added to the cluster. 818 | * 819 | * @param {google.maps.Marker} marker The marker to check. 820 | * @return {boolean} True if the marker is already added. 821 | */ 822 | Cluster.prototype.isMarkerAlreadyAdded = function(marker) { 823 | if (this.markers_.indexOf) { 824 | return this.markers_.indexOf(marker) != -1; 825 | } else { 826 | for (var i = 0, m; m = this.markers_[i]; i++) { 827 | if (m == marker) { 828 | return true; 829 | } 830 | } 831 | } 832 | return false; 833 | }; 834 | 835 | 836 | /** 837 | * Add a marker the cluster. 838 | * 839 | * @param {google.maps.Marker} marker The marker to add. 840 | * @return {boolean} True if the marker was added. 841 | */ 842 | Cluster.prototype.addMarker = function(marker) { 843 | if (this.isMarkerAlreadyAdded(marker)) { 844 | return false; 845 | } 846 | 847 | if (!this.center_) { 848 | this.center_ = marker.getPosition(); 849 | this.calculateBounds_(); 850 | } else { 851 | if (this.averageCenter_) { 852 | var l = this.markers_.length + 1; 853 | var lat = (this.center_.lat() * (l-1) + marker.getPosition().lat()) / l; 854 | var lng = (this.center_.lng() * (l-1) + marker.getPosition().lng()) / l; 855 | this.center_ = new google.maps.LatLng(lat, lng); 856 | this.calculateBounds_(); 857 | } 858 | } 859 | 860 | marker.isAdded = true; 861 | this.markers_.push(marker); 862 | 863 | var len = this.markers_.length; 864 | if (len < this.minClusterSize_ && marker.getMap() != this.map_) { 865 | // Min cluster size not reached so show the marker. 866 | marker.setMap(this.map_); 867 | } 868 | 869 | if (len == this.minClusterSize_) { 870 | // Hide the markers that were showing. 871 | for (var i = 0; i < len; i++) { 872 | this.markers_[i].setMap(null); 873 | } 874 | } 875 | 876 | if (len >= this.minClusterSize_) { 877 | marker.setMap(null); 878 | } 879 | 880 | this.updateIcon(); 881 | return true; 882 | }; 883 | 884 | 885 | /** 886 | * Returns the marker clusterer that the cluster is associated with. 887 | * 888 | * @return {MarkerClusterer} The associated marker clusterer. 889 | */ 890 | Cluster.prototype.getMarkerClusterer = function() { 891 | return this.markerClusterer_; 892 | }; 893 | 894 | 895 | /** 896 | * Returns the bounds of the cluster. 897 | * 898 | * @return {google.maps.LatLngBounds} the cluster bounds. 899 | */ 900 | Cluster.prototype.getBounds = function() { 901 | var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 902 | var markers = this.getMarkers(); 903 | for (var i = 0, marker; marker = markers[i]; i++) { 904 | bounds.extend(marker.getPosition()); 905 | } 906 | return bounds; 907 | }; 908 | 909 | 910 | /** 911 | * Removes the cluster 912 | */ 913 | Cluster.prototype.remove = function() { 914 | this.clusterIcon_.remove(); 915 | this.markers_.length = 0; 916 | delete this.markers_; 917 | }; 918 | 919 | 920 | /** 921 | * Returns the center of the cluster. 922 | * 923 | * @return {number} The cluster center. 924 | */ 925 | Cluster.prototype.getSize = function() { 926 | return this.markers_.length; 927 | }; 928 | 929 | 930 | /** 931 | * Returns the center of the cluster. 932 | * 933 | * @return {Array.} The cluster center. 934 | */ 935 | Cluster.prototype.getMarkers = function() { 936 | return this.markers_; 937 | }; 938 | 939 | 940 | /** 941 | * Returns the center of the cluster. 942 | * 943 | * @return {google.maps.LatLng} The cluster center. 944 | */ 945 | Cluster.prototype.getCenter = function() { 946 | return this.center_; 947 | }; 948 | 949 | 950 | /** 951 | * Calculated the extended bounds of the cluster with the grid. 952 | * 953 | * @private 954 | */ 955 | Cluster.prototype.calculateBounds_ = function() { 956 | var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 957 | this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); 958 | }; 959 | 960 | 961 | /** 962 | * Determines if a marker lies in the clusters bounds. 963 | * 964 | * @param {google.maps.Marker} marker The marker to check. 965 | * @return {boolean} True if the marker lies in the bounds. 966 | */ 967 | Cluster.prototype.isMarkerInClusterBounds = function(marker) { 968 | return this.bounds_.contains(marker.getPosition()); 969 | }; 970 | 971 | 972 | /** 973 | * Returns the map that the cluster is associated with. 974 | * 975 | * @return {google.maps.Map} The map. 976 | */ 977 | Cluster.prototype.getMap = function() { 978 | return this.map_; 979 | }; 980 | 981 | 982 | /** 983 | * Updates the cluster icon 984 | */ 985 | Cluster.prototype.updateIcon = function() { 986 | var zoom = this.map_.getZoom(); 987 | var mz = this.markerClusterer_.getMaxZoom(); 988 | 989 | if (mz && zoom > mz) { 990 | // The zoom is greater than our max zoom so show all the markers in cluster. 991 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 992 | marker.setMap(this.map_); 993 | } 994 | return; 995 | } 996 | 997 | if (this.markers_.length < this.minClusterSize_) { 998 | // Min cluster size not yet reached. 999 | this.clusterIcon_.hide(); 1000 | return; 1001 | } 1002 | 1003 | var numStyles = this.markerClusterer_.getStyles().length; 1004 | var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); 1005 | this.clusterIcon_.setCenter(this.center_); 1006 | this.clusterIcon_.setSums(sums); 1007 | this.clusterIcon_.show(); 1008 | }; 1009 | 1010 | 1011 | /** 1012 | * A cluster icon 1013 | * 1014 | * @param {Cluster} cluster The cluster to be associated with. 1015 | * @param {Object} styles An object that has style properties: 1016 | * 'url': (string) The image url. 1017 | * 'height': (number) The image height. 1018 | * 'width': (number) The image width. 1019 | * 'anchor': (Array) The anchor position of the label text. 1020 | * 'textColor': (string) The text color. 1021 | * 'textSize': (number) The text size. 1022 | * 'backgroundPosition: (string) The background postition x, y. 1023 | * @param {number=} opt_padding Optional padding to apply to the cluster icon. 1024 | * @constructor 1025 | * @extends google.maps.OverlayView 1026 | * @ignore 1027 | */ 1028 | function ClusterIcon(cluster, styles, opt_padding) { 1029 | cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); 1030 | 1031 | this.styles_ = styles; 1032 | this.padding_ = opt_padding || 0; 1033 | this.cluster_ = cluster; 1034 | this.center_ = null; 1035 | this.map_ = cluster.getMap(); 1036 | this.div_ = null; 1037 | this.sums_ = null; 1038 | this.visible_ = false; 1039 | 1040 | this.setMap(this.map_); 1041 | } 1042 | 1043 | 1044 | /** 1045 | * Triggers the clusterclick event and zoom's if the option is set. 1046 | * 1047 | * @param {google.maps.MouseEvent} event The event to propagate 1048 | */ 1049 | ClusterIcon.prototype.triggerClusterClick = function(event) { 1050 | var markerClusterer = this.cluster_.getMarkerClusterer(); 1051 | 1052 | // Trigger the clusterclick event. 1053 | google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_, event); 1054 | 1055 | if (markerClusterer.isZoomOnClick()) { 1056 | // Zoom into the cluster. 1057 | this.map_.fitBounds(this.cluster_.getBounds()); 1058 | this.map_.setCenter(this.cluster_.getCenter()); 1059 | } 1060 | }; 1061 | 1062 | 1063 | /** 1064 | * Adding the cluster icon to the dom. 1065 | * @ignore 1066 | */ 1067 | ClusterIcon.prototype.onAdd = function() { 1068 | this.div_ = document.createElement('DIV'); 1069 | if (this.visible_) { 1070 | var pos = this.getPosFromLatLng_(this.center_); 1071 | this.div_.style.cssText = this.createCss(pos); 1072 | this.div_.innerHTML = this.sums_.text; 1073 | } 1074 | 1075 | var panes = this.getPanes(); 1076 | panes.overlayMouseTarget.appendChild(this.div_); 1077 | 1078 | var that = this; 1079 | var isDragging = false; 1080 | google.maps.event.addDomListener(this.div_, 'click', function(event) { 1081 | // Only perform click when not preceded by a drag 1082 | if (!isDragging) { 1083 | that.triggerClusterClick(event); 1084 | } 1085 | }); 1086 | google.maps.event.addDomListener(this.div_, 'mousedown', function() { 1087 | isDragging = false; 1088 | }); 1089 | google.maps.event.addDomListener(this.div_, 'mousemove', function() { 1090 | isDragging = true; 1091 | }); 1092 | }; 1093 | 1094 | 1095 | /** 1096 | * Returns the position to place the div dending on the latlng. 1097 | * 1098 | * @param {google.maps.LatLng} latlng The position in latlng. 1099 | * @return {google.maps.Point} The position in pixels. 1100 | * @private 1101 | */ 1102 | ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { 1103 | var pos = this.getProjection().fromLatLngToDivPixel(latlng); 1104 | 1105 | if (typeof this.iconAnchor_ === 'object' && this.iconAnchor_.length === 2) { 1106 | pos.x -= this.iconAnchor_[0]; 1107 | pos.y -= this.iconAnchor_[1]; 1108 | } else { 1109 | pos.x -= parseInt(this.width_ / 2, 10); 1110 | pos.y -= parseInt(this.height_ / 2, 10); 1111 | } 1112 | return pos; 1113 | }; 1114 | 1115 | 1116 | /** 1117 | * Draw the icon. 1118 | * @ignore 1119 | */ 1120 | ClusterIcon.prototype.draw = function() { 1121 | if (this.visible_) { 1122 | var pos = this.getPosFromLatLng_(this.center_); 1123 | this.div_.style.top = pos.y + 'px'; 1124 | this.div_.style.left = pos.x + 'px'; 1125 | } 1126 | }; 1127 | 1128 | 1129 | /** 1130 | * Hide the icon. 1131 | */ 1132 | ClusterIcon.prototype.hide = function() { 1133 | if (this.div_) { 1134 | this.div_.style.display = 'none'; 1135 | } 1136 | this.visible_ = false; 1137 | }; 1138 | 1139 | 1140 | /** 1141 | * Position and show the icon. 1142 | */ 1143 | ClusterIcon.prototype.show = function() { 1144 | if (this.div_) { 1145 | var pos = this.getPosFromLatLng_(this.center_); 1146 | this.div_.style.cssText = this.createCss(pos); 1147 | this.div_.style.display = ''; 1148 | } 1149 | this.visible_ = true; 1150 | }; 1151 | 1152 | 1153 | /** 1154 | * Remove the icon from the map 1155 | */ 1156 | ClusterIcon.prototype.remove = function() { 1157 | this.setMap(null); 1158 | }; 1159 | 1160 | 1161 | /** 1162 | * Implementation of the onRemove interface. 1163 | * @ignore 1164 | */ 1165 | ClusterIcon.prototype.onRemove = function() { 1166 | if (this.div_ && this.div_.parentNode) { 1167 | this.hide(); 1168 | this.div_.parentNode.removeChild(this.div_); 1169 | this.div_ = null; 1170 | } 1171 | }; 1172 | 1173 | 1174 | /** 1175 | * Set the sums of the icon. 1176 | * 1177 | * @param {Object} sums The sums containing: 1178 | * 'text': (string) The text to display in the icon. 1179 | * 'index': (number) The style index of the icon. 1180 | */ 1181 | ClusterIcon.prototype.setSums = function(sums) { 1182 | this.sums_ = sums; 1183 | this.text_ = sums.text; 1184 | this.index_ = sums.index; 1185 | if (this.div_) { 1186 | this.div_.innerHTML = sums.text; 1187 | } 1188 | 1189 | this.useStyle(); 1190 | }; 1191 | 1192 | 1193 | /** 1194 | * Sets the icon to the the styles. 1195 | */ 1196 | ClusterIcon.prototype.useStyle = function() { 1197 | var index = Math.max(0, this.sums_.index - 1); 1198 | index = Math.min(this.styles_.length - 1, index); 1199 | var style = this.styles_[index]; 1200 | this.url_ = style['url']; 1201 | this.height_ = style['height']; 1202 | this.width_ = style['width']; 1203 | this.textColor_ = style['textColor']; 1204 | this.anchor_ = style['anchor']; 1205 | this.textSize_ = style['textSize']; 1206 | this.backgroundPosition_ = style['backgroundPosition']; 1207 | this.iconAnchor_ = style['iconAnchor']; 1208 | }; 1209 | 1210 | 1211 | /** 1212 | * Sets the center of the icon. 1213 | * 1214 | * @param {google.maps.LatLng} center The latlng to set as the center. 1215 | */ 1216 | ClusterIcon.prototype.setCenter = function(center) { 1217 | this.center_ = center; 1218 | }; 1219 | 1220 | 1221 | /** 1222 | * Create the css text based on the position of the icon. 1223 | * 1224 | * @param {google.maps.Point} pos The position. 1225 | * @return {string} The css style text. 1226 | */ 1227 | ClusterIcon.prototype.createCss = function(pos) { 1228 | var style = []; 1229 | style.push('background-image:url(' + this.url_ + ');'); 1230 | var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0'; 1231 | style.push('background-position:' + backgroundPosition + ';'); 1232 | 1233 | if (typeof this.anchor_ === 'object') { 1234 | if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && 1235 | this.anchor_[0] < this.height_) { 1236 | style.push('height:' + (this.height_ - this.anchor_[0]) + 1237 | 'px; padding-top:' + this.anchor_[0] + 'px;'); 1238 | } else if (typeof this.anchor_[0] === 'number' && this.anchor_[0] < 0 && 1239 | -this.anchor_[0] < this.height_) { 1240 | style.push('height:' + this.height_ + 'px; line-height:' + (this.height_ + this.anchor_[0]) + 1241 | 'px;'); 1242 | } else { 1243 | style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + 1244 | 'px;'); 1245 | } 1246 | if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && 1247 | this.anchor_[1] < this.width_) { 1248 | style.push('width:' + (this.width_ - this.anchor_[1]) + 1249 | 'px; padding-left:' + this.anchor_[1] + 'px;'); 1250 | } else { 1251 | style.push('width:' + this.width_ + 'px; text-align:center;'); 1252 | } 1253 | } else { 1254 | style.push('height:' + this.height_ + 'px; line-height:' + 1255 | this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); 1256 | } 1257 | 1258 | var txtColor = this.textColor_ ? this.textColor_ : 'black'; 1259 | var txtSize = this.textSize_ ? this.textSize_ : 11; 1260 | 1261 | style.push('cursor:pointer; top:' + pos.y + 'px; left:' + 1262 | pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + 1263 | txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold'); 1264 | return style.join(''); 1265 | }; 1266 | 1267 | 1268 | // Export Symbols for Closure 1269 | // If you are not going to compile with closure then you can remove the 1270 | // code below. 1271 | window['MarkerClusterer'] = MarkerClusterer; 1272 | MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker; 1273 | MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers; 1274 | MarkerClusterer.prototype['clearMarkers'] = 1275 | MarkerClusterer.prototype.clearMarkers; 1276 | MarkerClusterer.prototype['fitMapToMarkers'] = 1277 | MarkerClusterer.prototype.fitMapToMarkers; 1278 | MarkerClusterer.prototype['getCalculator'] = 1279 | MarkerClusterer.prototype.getCalculator; 1280 | MarkerClusterer.prototype['getGridSize'] = 1281 | MarkerClusterer.prototype.getGridSize; 1282 | MarkerClusterer.prototype['getExtendedBounds'] = 1283 | MarkerClusterer.prototype.getExtendedBounds; 1284 | MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap; 1285 | MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers; 1286 | MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom; 1287 | MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles; 1288 | MarkerClusterer.prototype['getTotalClusters'] = 1289 | MarkerClusterer.prototype.getTotalClusters; 1290 | MarkerClusterer.prototype['getTotalMarkers'] = 1291 | MarkerClusterer.prototype.getTotalMarkers; 1292 | MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw; 1293 | MarkerClusterer.prototype['removeMarker'] = 1294 | MarkerClusterer.prototype.removeMarker; 1295 | MarkerClusterer.prototype['removeMarkers'] = 1296 | MarkerClusterer.prototype.removeMarkers; 1297 | MarkerClusterer.prototype['resetViewport'] = 1298 | MarkerClusterer.prototype.resetViewport; 1299 | MarkerClusterer.prototype['repaint'] = 1300 | MarkerClusterer.prototype.repaint; 1301 | MarkerClusterer.prototype['setCalculator'] = 1302 | MarkerClusterer.prototype.setCalculator; 1303 | MarkerClusterer.prototype['setGridSize'] = 1304 | MarkerClusterer.prototype.setGridSize; 1305 | MarkerClusterer.prototype['setMaxZoom'] = 1306 | MarkerClusterer.prototype.setMaxZoom; 1307 | MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd; 1308 | MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw; 1309 | 1310 | Cluster.prototype['getCenter'] = Cluster.prototype.getCenter; 1311 | Cluster.prototype['getSize'] = Cluster.prototype.getSize; 1312 | Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers; 1313 | 1314 | ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd; 1315 | ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw; 1316 | ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove; 1317 | --------------------------------------------------------------------------------