├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── Jakefile.js ├── MIT-LICENCE.txt ├── README.md ├── bower.json ├── build ├── hintrc.js └── rollup-config.js ├── dist ├── MarkerCluster.Default.css ├── MarkerCluster.css └── WhereAreTheJavascriptFiles.txt ├── example ├── geojson-sample.js ├── geojson.html ├── map.png ├── marker-clustering-convexhull.html ├── marker-clustering-custom.html ├── marker-clustering-dragging.html ├── marker-clustering-everything.html ├── marker-clustering-geojson.html ├── marker-clustering-pane.html ├── marker-clustering-realworld-maxzoom.388.html ├── marker-clustering-realworld-mobile.388.html ├── marker-clustering-realworld.10000.html ├── marker-clustering-realworld.388.html ├── marker-clustering-realworld.50000.html ├── marker-clustering-singlemarkermode.html ├── marker-clustering-spiderfier.html ├── marker-clustering-zoomtobounds.html ├── marker-clustering-zoomtoshowlayer.html ├── marker-clustering.html ├── mobile.css ├── old-bugs │ ├── add-1000-after.html │ ├── add-markers-offscreen.html │ ├── add-remove-before-addtomap.html │ ├── animationless-zoom.html │ ├── click-cluster-at-screen-edge.html │ ├── disappearing-marker-from-spider.html │ ├── doesnt-update-cluster-on-bottom-level.html │ ├── drag-with-spiderfying.html │ ├── remove-add-clustering.html │ ├── remove-when-spiderfied.html │ ├── removelayer-after-remove-from-map.html │ ├── setView-doesnt-remove.html │ ├── zoomtoshowlayer-doesnt-need-to-zoom.html │ └── zoomtoshowlayer-doesnt-zoom-if-centered-on.html ├── realworld.10000.js ├── realworld.388.js ├── realworld.50000.1.js ├── realworld.50000.2.js ├── remove-geoJSON-when-spiderfied.html └── screen.css ├── package-lock.json ├── package.json ├── spec ├── after.js ├── expect.js ├── index.html ├── karma.conf.js ├── sinon.js └── suites │ ├── AddLayer.MultipleSpec.js │ ├── AddLayer.SingleSpec.js │ ├── AddLayersSpec.js │ ├── ChildChangingIconSupportSpec.js │ ├── CircleMarkerSupportSpec.js │ ├── CircleSupportSpec.js │ ├── DistanceGridSpec.js │ ├── LeafletSpec.js │ ├── NonPointSpec.js │ ├── PaneSpec.js │ ├── QuickHullSpec.js │ ├── RefreshSpec.js │ ├── RememberOpacity.js │ ├── RemoveLayerSpec.js │ ├── SpecHelper.js │ ├── animateOptionSpec.js │ ├── clearLayersSpec.js │ ├── disableClusteringAtZoomSpec.js │ ├── eachLayerSpec.js │ ├── eventsSpec.js │ ├── getBoundsSpec.js │ ├── getLayersSpec.js │ ├── getVisibleParentSpec.js │ ├── markerMoveSupportSpec.js │ ├── nonIntegerZoomSpec.js │ ├── onAddSpec.js │ ├── onRemoveSpec.js │ ├── removeLayersSpec.js │ ├── removeOutsideVisibleBoundsSpec.js │ ├── singleMarkerModeSpec.js │ ├── spiderfySpec.js │ ├── supportNegativeZoomSpec.js │ ├── unspiderfySpec.js │ └── zoomAnimationSpec.js └── src ├── DistanceGrid.js ├── MarkerCluster.QuickHull.js ├── MarkerCluster.Spiderfier.js ├── MarkerCluster.js ├── MarkerClusterGroup.Refresh.js ├── MarkerClusterGroup.js ├── MarkerOpacity.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | bin 3 | obj 4 | 5 | # mstest test results 6 | TestResults 7 | node_modules 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 8 5 | cache: 6 | directories: 7 | - "travis_phantomjs" 8 | - "$HOME/.npm" 9 | 10 | before_install: 11 | - "export PHANTOMJS_VERSION=2.1.1" 12 | - "hash -r" 13 | - "phantomjs --version" 14 | - "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH" 15 | - "hash -r" 16 | - "phantomjs --version" 17 | - "if [ $(phantomjs --version) != \"$PHANTOMJS_VERSION\" ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi" 18 | - "hash -r" 19 | - "if [ $(phantomjs --version) != \"$PHANTOMJS_VERSION\" ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi" 20 | - "if [ $(phantomjs --version) != \"$PHANTOMJS_VERSION\" ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi" 21 | - "hash -r" 22 | - "phantomjs --version" 23 | 24 | install: 25 | - npm ci 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Leaflet.MarkerCluster 2 | ===================================== 3 | 4 | 1. [Reporting Bugs](#reporting-bugs) 5 | 2. [Contributing Code](#contributing-code) 6 | 3. [Building](#building) 7 | 4. [Testing](#testing) 8 | 9 | ## Reporting Bugs 10 | 11 | Before reporting a bug on the project's [issues page](https://github.com/Leaflet/Leaflet.markercluster/issues), 12 | first make sure that your issue is caused by Leaflet.MarkerCluster, not your application code 13 | (e.g. passing incorrect arguments to methods, etc.). 14 | Second, search the already reported issues for similar cases, 15 | and if it's already reported, just add any additional details in the comments. 16 | 17 | After you've made sure that you've found a new Leaflet.markercluster bug, 18 | here are some tips for creating a helpful report that will make fixing it much easier and quicker: 19 | 20 | * Write a **descriptive, specific title**. Bad: *Problem with polylines*. Good: *Doing X in IE9 causes Z*. 21 | * Include **browser, OS and Leaflet version** info in the description. 22 | * Create a **simple test case** that demonstrates the bug (e.g. using [JSFiddle](http://jsfiddle.net/) or [JS Bin](http://jsbin.com/)). 23 | * Check whether the bug can be reproduced in **other browsers**. 24 | * Check if the bug occurs in the stable version, master, or both. 25 | * *Bonus tip:* if the bug only appears in the master version but the stable version is fine, 26 | use `git bisect` to find the exact commit that introduced the bug. 27 | 28 | If you just want some help with your project, 29 | try asking [on the Leaflet forum](https://groups.google.com/forum/#!forum/leaflet-js) instead. 30 | 31 | ## Contributing Code 32 | 33 | ### Considerations for Accepting Patches 34 | 35 | While we happily accept patches, we're also committed to keeping Leaflet simple, lightweight and blazingly fast. 36 | So bugfixes, performance optimizations and small improvements that don't add a lot of code 37 | are much more likely to get accepted quickly. 38 | 39 | Before sending a pull request with a new feature, check if it's been discussed before already 40 | (either on [GitHub issues](https://github.com/Leaflet/Leaflet/issues) 41 | or [Leaflet UserVoice](http://leaflet.uservoice.com/)), 42 | and ask yourself two questions: 43 | 44 | 1. Are you sure that this new feature is important enough to justify its presence in the Leaflet core? 45 | Or will it look better as a plugin in a separate repository? 46 | 2. Is it written in a simple, concise way that doesn't add bulk to the codebase? 47 | 48 | If your feature or API improvement did get merged into master, 49 | please consider submitting another pull request with the corresponding [documentation update](#improving-documentation). 50 | 51 | ## Building 52 | 53 | Install the dependencies: 54 | ``` 55 | npm install -g jake 56 | npm install 57 | ``` 58 | 59 | Then to build: 60 | ``` 61 | jake 62 | ``` 63 | Output will be in the ```dist/``` directory 64 | 65 | ## Testing 66 | 67 | To run unit tests: 68 | ``` 69 | jake test 70 | ``` 71 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - [ ] I'm reporting a bug, not asking for help 2 | - [ ] I'm sure this is a Leaflet.MarkerCluster code issue, not an issue with my own code nor with the framework I'm using (Cordova, Ionic, Angular, React…) 3 | - [ ] I've searched through the issues to make sure it's not yet reported 4 | 5 | ---- 6 | 7 | ## How to reproduce 8 | 9 | - Leaflet version I'm using: 10 | - Leaflet.MarkerCluster version I'm using: 11 | - Browser (with version) I'm using: 12 | - OS/Platform (with version) I'm using: 13 | - step 1 14 | - step 2 15 | 16 | ## What behaviour I'm expecting and which behaviour I'm seeing 17 | 18 | ## Minimal example reproducing the issue 19 | 20 | - [ ] this example is as simple as possible 21 | - [ ] this example does not rely on any third party code 22 | 23 | Using http://leafletjs.com/edit.html or any other jsfiddle-like site. 24 | -------------------------------------------------------------------------------- /Jakefile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Leaflet.markercluster building, testing and linting scripts. 3 | 4 | To use, install Node, then run the following commands in the project root: 5 | 6 | npm install -g jake 7 | npm install 8 | 9 | To check the code for errors and build Leaflet from source, run "jake". 10 | To run the tests, run "jake test". 11 | 12 | For a custom build, open build/build.html in the browser and follow the instructions. 13 | */ 14 | 15 | var path = require('path'); 16 | 17 | desc('Check Leaflet.markercluster source for errors with JSHint'); 18 | task('lint', { 19 | async: true 20 | }, function(){ 21 | jake.exec('jshint', { 22 | printStdout: true 23 | }, function () { 24 | console.log('\tCheck passed.\n'); 25 | complete(); 26 | }); 27 | }); 28 | 29 | desc('Combine Leaflet.markercluster source files'); 30 | task('build', ['lint'], { 31 | async: true 32 | }, function(){ 33 | jake.exec('npm run-script rollup', function() { 34 | console.log('Rolled up.'); 35 | complete(); 36 | }); 37 | }); 38 | 39 | desc('Compress bundled files'); 40 | task('uglify', ['build'], function(){ 41 | jake.exec('npm run-script uglify', function() { console.log('Uglyfied.'); }); 42 | }); 43 | 44 | desc('Run PhantomJS tests'); 45 | task('test', ['lint'], function() { 46 | 47 | var karma = require('karma'), 48 | testConfig = {configFile : path.join(__dirname, './spec/karma.conf.js')}; 49 | 50 | testConfig.browsers = ['PhantomJS']; 51 | 52 | function isArgv(optName) { 53 | return process.argv.indexOf(optName) !== -1; 54 | } 55 | 56 | if (isArgv('--chrome')) { 57 | testConfig.browsers.push('Chrome'); 58 | } 59 | if (isArgv('--safari')) { 60 | testConfig.browsers.push('Safari'); 61 | } 62 | if (isArgv('--ff')) { 63 | testConfig.browsers.push('Firefox'); 64 | } 65 | if (isArgv('--ie')) { 66 | testConfig.browsers.push('IE'); 67 | } 68 | 69 | if (isArgv('--cov')) { 70 | testConfig.preprocessors = { 71 | 'src/**/*.js': 'coverage' 72 | }; 73 | testConfig.coverageReporter = { 74 | type : 'html', 75 | dir : 'coverage/' 76 | }; 77 | testConfig.reporters = ['coverage']; 78 | } 79 | 80 | console.log('Running tests...'); 81 | 82 | var server = new karma.Server(testConfig, function(exitCode) { 83 | if (!exitCode) { 84 | console.log('\tTests ran successfully.\n'); 85 | complete(); 86 | } else { 87 | process.exit(exitCode); 88 | } 89 | }); 90 | server.start(); 91 | }); 92 | 93 | task('default', ['build', 'uglify']); 94 | -------------------------------------------------------------------------------- /MIT-LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 David Leaver 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet.markercluster", 3 | "version": "1.5.4", 4 | "homepage": "https://github.com/Leaflet/Leaflet.markercluster", 5 | "authors": [ 6 | "Dave Leaver " 7 | ], 8 | "description": "Marker Clustering plugin for Leaflet.", 9 | "main": [ 10 | "dist/leaflet.markercluster-src.js", 11 | "dist/MarkerCluster.css", 12 | "dist/MarkerCluster.Default.css" 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "example", 20 | "spec", 21 | "test", 22 | "tests" 23 | ], 24 | "dependencies": { 25 | "leaflet": ">= 1.3.1" 26 | } 27 | } -------------------------------------------------------------------------------- /build/hintrc.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | 3 | // environment 4 | "browser": true, 5 | "node": true, 6 | "predef": ['L', 'define'], 7 | "strict": false, 8 | 9 | // code style 10 | "bitwise": true, 11 | "camelcase": true, 12 | "curly": true, 13 | "eqeqeq": true, 14 | "forin": false, 15 | "immed": true, 16 | "latedef": true, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": true, 20 | "nonew": true, 21 | "undef": true, 22 | "unused": true, 23 | //"quotmark": "single", 24 | 25 | // whitespace 26 | "indent": 4, 27 | "trailing": true, 28 | "white": true, 29 | "smarttabs": true, 30 | //"maxlen": 120 31 | 32 | // code simplicity - not enforced but nice to check from time to time 33 | // "maxstatements": 20, 34 | // "maxcomplexity": 5 35 | // "maxparams": 4, 36 | // "maxdepth": 4 37 | }; 38 | -------------------------------------------------------------------------------- /build/rollup-config.js: -------------------------------------------------------------------------------- 1 | 2 | // Config file for running Rollup in "normal" mode (non-watch) 3 | 4 | import rollupGitVersion from 'rollup-plugin-git-version' 5 | import json from 'rollup-plugin-json' 6 | 7 | import gitRev from 'git-rev-sync' 8 | 9 | 10 | let version = require('../package.json').version; 11 | let release; 12 | 13 | // Skip the git branch+rev in the banner when doing a release build 14 | if (process.env.NODE_ENV === 'release') { 15 | release = true; 16 | } else { 17 | release = false; 18 | const branch = gitRev.branch(); 19 | const rev = gitRev.short(); 20 | version += '+' + branch + '.' + rev; 21 | } 22 | 23 | const banner = `/* 24 | * Leaflet.markercluster ` + version + `, 25 | * Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps. 26 | * https://github.com/Leaflet/Leaflet.markercluster 27 | * (c) 2012-2017, Dave Leaver, smartrak 28 | */`; 29 | 30 | export default { 31 | input: 'src/index.js', 32 | output: { 33 | banner, 34 | file: 'dist/leaflet.markercluster-src.js', 35 | format: 'umd', 36 | legacy: true, // Needed to create files loadable by IE8 37 | name: 'Leaflet.markercluster', 38 | sourcemap: true, 39 | }, 40 | plugins: [ 41 | release ? json() : rollupGitVersion(), 42 | ], 43 | }; 44 | -------------------------------------------------------------------------------- /dist/MarkerCluster.Default.css: -------------------------------------------------------------------------------- 1 | .marker-cluster-small { 2 | background-color: rgba(181, 226, 140, 0.6); 3 | } 4 | .marker-cluster-small div { 5 | background-color: rgba(110, 204, 57, 0.6); 6 | } 7 | 8 | .marker-cluster-medium { 9 | background-color: rgba(241, 211, 87, 0.6); 10 | } 11 | .marker-cluster-medium div { 12 | background-color: rgba(240, 194, 12, 0.6); 13 | } 14 | 15 | .marker-cluster-large { 16 | background-color: rgba(253, 156, 115, 0.6); 17 | } 18 | .marker-cluster-large div { 19 | background-color: rgba(241, 128, 23, 0.6); 20 | } 21 | 22 | /* IE 6-8 fallback colors */ 23 | .leaflet-oldie .marker-cluster-small { 24 | background-color: rgb(181, 226, 140); 25 | } 26 | .leaflet-oldie .marker-cluster-small div { 27 | background-color: rgb(110, 204, 57); 28 | } 29 | 30 | .leaflet-oldie .marker-cluster-medium { 31 | background-color: rgb(241, 211, 87); 32 | } 33 | .leaflet-oldie .marker-cluster-medium div { 34 | background-color: rgb(240, 194, 12); 35 | } 36 | 37 | .leaflet-oldie .marker-cluster-large { 38 | background-color: rgb(253, 156, 115); 39 | } 40 | .leaflet-oldie .marker-cluster-large div { 41 | background-color: rgb(241, 128, 23); 42 | } 43 | 44 | .marker-cluster { 45 | background-clip: padding-box; 46 | border-radius: 20px; 47 | } 48 | .marker-cluster div { 49 | width: 30px; 50 | height: 30px; 51 | margin-left: 5px; 52 | margin-top: 5px; 53 | 54 | text-align: center; 55 | border-radius: 15px; 56 | font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif; 57 | } 58 | .marker-cluster span { 59 | line-height: 30px; 60 | } -------------------------------------------------------------------------------- /dist/MarkerCluster.css: -------------------------------------------------------------------------------- 1 | .leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow { 2 | -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in; 3 | -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in; 4 | -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in; 5 | transition: transform 0.3s ease-out, opacity 0.3s ease-in; 6 | } 7 | 8 | .leaflet-cluster-spider-leg { 9 | /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */ 10 | -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in; 11 | -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in; 12 | -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in; 13 | transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in; 14 | } 15 | -------------------------------------------------------------------------------- /dist/WhereAreTheJavascriptFiles.txt: -------------------------------------------------------------------------------- 1 | We don't ship the .js files in the git master branch. 2 | They are only present in version tags and in npm. 3 | 4 | See how to get the JS files here: https://github.com/Leaflet/Leaflet.markercluster#using-the-plugin 5 | Or how to build them: https://github.com/Leaflet/Leaflet.markercluster#building-testing-and-linting-scripts 6 | -------------------------------------------------------------------------------- /example/geojson-sample.js: -------------------------------------------------------------------------------- 1 | var geojsonSample = { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [102.0, 0.5] 9 | }, 10 | "properties": { 11 | "prop0": "value0", 12 | "color": "blue" 13 | } 14 | }, 15 | 16 | { 17 | "type": "Feature", 18 | "geometry": { 19 | "type": "LineString", 20 | "coordinates": [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]] 21 | }, 22 | "properties": { 23 | "color": "red", 24 | "prop1": 0.0 25 | } 26 | }, 27 | 28 | { 29 | "type": "Feature", 30 | "geometry": { 31 | "type": "Polygon", 32 | "coordinates": [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]] 33 | }, 34 | "properties": { 35 | "color": "green", 36 | "prop1": { 37 | "this": "that" 38 | } 39 | } 40 | }, 41 | 42 | { 43 | "type": "Feature", 44 | "geometry": { 45 | "type": "MultiPolygon", 46 | "coordinates": [[[[100.0, 1.5], [100.5, 1.5], [100.5, 2.0], [100.0, 2.0], [100.0, 1.5]]], [[[100.5, 2.0], [100.5, 2.5], [101.0, 2.5], [101.0, 2.0], [100.5, 2.0]]]] 47 | }, 48 | "properties": { 49 | "color": "purple" 50 | } 51 | } 52 | ] 53 | }; 54 | -------------------------------------------------------------------------------- /example/geojson.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /example/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leaflet/Leaflet.markercluster/c0f055bd5dcd1d6a733090d0cf024d7362d77bc8/example/map.png -------------------------------------------------------------------------------- /example/marker-clustering-convexhull.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /example/marker-clustering-custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 |
28 | 29 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /example/marker-clustering-dragging.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /example/marker-clustering-everything.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | Mouse over a cluster to see the bounds of its children and click a cluster to zoom to those bounds 21 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /example/marker-clustering-geojson.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | Mouse over a cluster to see the bounds of its children and click a cluster to zoom to those bounds 21 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /example/marker-clustering-pane.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 | 34 |
35 | 36 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /example/marker-clustering-realworld-maxzoom.388.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | Markers will show on the bottom 2 zoom levels even though the markers would normally cluster. 21 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/marker-clustering-realworld-mobile.388.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /example/marker-clustering-realworld.10000.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | Mouse over a cluster to see the bounds of its children and click a cluster to zoom to those bounds 22 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /example/marker-clustering-realworld.388.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | Mouse over a cluster to see the bounds of its children and click a cluster to zoom to those bounds 21 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/marker-clustering-realworld.50000.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | Mouse over a cluster to see the bounds of its children and click a cluster to zoom to those bounds 24 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /example/marker-clustering-singlemarkermode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | Click a cluster to zoom to its bounds 19 | 20 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /example/marker-clustering-spiderfier.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /example/marker-clustering-zoomtobounds.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | Click a cluster to zoom to its bounds 19 | 20 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /example/marker-clustering-zoomtoshowlayer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | When clicked we will zoom down to a marker, spiderfying if required to show it and then open its popup 22 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /example/marker-clustering.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/mobile.css: -------------------------------------------------------------------------------- 1 | html, body, #map { 2 | margin: 0; 3 | padding: 0; 4 | width: 100%; 5 | height: 100%; 6 | } -------------------------------------------------------------------------------- /example/old-bugs/add-1000-after.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | Bug #51. Click the button. It will add 1000 markers to the map. this should be fast, but previously in (non-IE browsers) it was very slow.
24 | Bug #43. Improving performance more.
25 | 26 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /example/old-bugs/add-markers-offscreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | Bug #69. Click the button 2+ times. Zoom out. Should just be a single cluster but instead one of the child markers is still visible.
24 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/old-bugs/add-remove-before-addtomap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | Bug #64. Nothing should appear on the map.
23 | 24 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /example/old-bugs/animationless-zoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | Bug #216. Click the button. It will zoom in, leaflet will not do an animation for the zoom. A marker should be visible.
24 | 25 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/old-bugs/click-cluster-at-screen-edge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | Bug #344. Click the cluster at the screen edge. Map will zoom to it and its markers will appear, but it will not disappear.
23 | 24 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /example/old-bugs/disappearing-marker-from-spider.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | Click on the cluster to spiderfy and then
19 |
20 |
Note: The marker on the old cluster position comes back on next move or on map scrolling.
21 | 22 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /example/old-bugs/doesnt-update-cluster-on-bottom-level.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | Bug #114. Markers are added to the map periodically using addLayers. Bug was that after becoming a cluster (size 2 or 3 usually) they would never change again even if more markers were added to them.
24 | 25 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /example/old-bugs/drag-with-spiderfying.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | Bug #907. Drag a marker from a spiderfied cluster over other clusters.
19 | Bug #808. Drag a marker and while dragging zoom out with scroll-wheel.
20 | 21 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /example/old-bugs/remove-add-clustering.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

Whenever a marker is clicked it is removed from the clusterer and added directly to the map instead.

23 |

Click Marker on Left, zoom out 1 layer, click marker on right.

24 |

Expected behaviour: Both markers are shown. Bugged behaviour: Both markers are on map with opacity 0.

25 |

26 | 
27 | 	
73 | 
74 | 
75 | 


--------------------------------------------------------------------------------
/example/old-bugs/remove-when-spiderfied.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 	Leaflet debug page
 5 | 
 6 | 	
 7 | 	
 8 | 	
 9 | 	
10 | 
11 | 	
12 | 	
13 | 	
14 | 	
15 | 	
16 | 	
17 | 	
18 | 
19 | 
20 | 
21 | 	
22 |
23 | Bug #54. Spiderfy the cluster then click the button. Should result in 2 markers right beside each other on the map.
24 | Bug #53. Spiderfy the cluster then click the button. Spider lines remain on the map.
25 | Bug #49. Spiderfy the cluster then click the second button. Spider lines remain on the map. Click the map to get an error. 26 | 27 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /example/old-bugs/removelayer-after-remove-from-map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 1 - Swap layers
23 | 2 - Remove all markers
24 | 3 - Swap layers again => Marker is still there
25 | 26 | Bug
#160. Click 1,2,3. There should be nothing on the map.
27 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /example/old-bugs/setView-doesnt-remove.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | Bug #63. Zoom down on the very left side untill markers are visible. Click the button. Scroll to the left in one go, those markers should be in clusters but the actual markers will still be visible.
24 | 25 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /example/old-bugs/zoomtoshowlayer-doesnt-need-to-zoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | Bug #65. Click 2 then click the button. You should be scrolled to the marker, old behaviour would zoom you out.
24 | 25 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /example/old-bugs/zoomtoshowlayer-doesnt-zoom-if-centered-on.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | Bug #286 (from @Grsmto). Click the button. The cluster should spiderfy and show the popup, old behaviour did nothing.
24 | 25 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /example/remove-geoJSON-when-spiderfied.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet debug page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | New Bug. Spiderfy the cluster then click the button #1. All markers disapear, but it should remain marker #2.
20 | New Bug. Spiderfy the cluster then click the button #2. All markers disapear, but it should remain marker #1.
21 | 22 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /example/screen.css: -------------------------------------------------------------------------------- 1 | #map { 2 | width: 800px; 3 | height: 600px; 4 | border: 1px solid #ccc; 5 | } 6 | 7 | #progress { 8 | display: none; 9 | position: absolute; 10 | z-index: 1000; 11 | left: 400px; 12 | top: 300px; 13 | width: 200px; 14 | height: 20px; 15 | margin-top: -20px; 16 | margin-left: -100px; 17 | background-color: #fff; 18 | background-color: rgba(255, 255, 255, 0.7); 19 | border-radius: 4px; 20 | padding: 2px; 21 | } 22 | 23 | #progress-bar { 24 | width: 0; 25 | height: 100%; 26 | background-color: #76A6FC; 27 | border-radius: 4px; 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet.markercluster", 3 | "repository": "https://github.com/Leaflet/Leaflet.markercluster", 4 | "version": "1.5.4", 5 | "description": "Provides Beautiful Animated Marker Clustering functionality for Leaflet", 6 | "devDependencies": { 7 | "git-rev-sync": "^1.8.0", 8 | "happen": "^0.3.1", 9 | "jake": "8.0.19", 10 | "jshint": "^2.9.4", 11 | "karma": "4.0.0", 12 | "karma-chrome-launcher": "^2.0.0", 13 | "karma-coverage": "^1.1.1", 14 | "karma-firefox-launcher": "^1.0.1", 15 | "karma-mocha": "^1.3.0", 16 | "karma-phantomjs-launcher": "^1.0.4", 17 | "karma-rollup-preprocessor": "7.0.0", 18 | "karma-safari-launcher": "^1.0.0", 19 | "leaflet": "^1.3.1", 20 | "mocha": "6.0.2", 21 | "phantomjs-prebuilt": "^2.1.14", 22 | "rollup": "1.3.2", 23 | "rollup-plugin-git-version": "0.2.1", 24 | "rollup-plugin-json": "3.1.0", 25 | "uglify-js": "3.4.9" 26 | }, 27 | "peerDependencies": { 28 | "leaflet": "^1.3.1" 29 | }, 30 | "main": "dist/leaflet.markercluster-src.js", 31 | "style": "dist/MarkerCluster.css", 32 | "scripts": { 33 | "test": "karma start ./spec/karma.conf.js", 34 | "prepublish": "jake", 35 | "rollup": "rollup -c build/rollup-config.js", 36 | "uglify": "uglifyjs dist/leaflet.markercluster-src.js -c -m -o dist/leaflet.markercluster.js --source-map \"filename=dist/leaflet.markercluster.js.map,content=dist/leaflet.markercluster-src.js.map,url=leaflet.markercluster.js.map\"" 37 | }, 38 | "keywords": [ 39 | "gis", 40 | "map", 41 | "cluster" 42 | ], 43 | "license": "MIT" 44 | } 45 | -------------------------------------------------------------------------------- /spec/after.js: -------------------------------------------------------------------------------- 1 | // put after Leaflet files as imagePath can't be detected in a PhantomJS env 2 | L.Icon.Default.imagePath = "../dist/images"; 3 | -------------------------------------------------------------------------------- /spec/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /spec/karma.conf.js: -------------------------------------------------------------------------------- 1 | var json = require('rollup-plugin-json'); 2 | 3 | // Karma configuration 4 | module.exports = function (config) { 5 | 6 | // var libSources = require(__dirname + '/../build/build.js').getFiles(); 7 | 8 | var files = [ 9 | "spec/sinon.js", 10 | "spec/expect.js", 11 | 12 | "node_modules/leaflet/dist/leaflet-src.js", 13 | "src/index.js", 14 | 15 | "spec/after.js", 16 | "node_modules/happen/happen.js", 17 | "spec/suites/SpecHelper.js", 18 | "spec/suites/**/*.js", 19 | "dist/*.css" 20 | ]; 21 | 22 | config.set({ 23 | // base path, that will be used to resolve files and exclude 24 | basePath: '../', 25 | 26 | plugins: [ 27 | 'karma-rollup-preprocessor', 28 | 'karma-mocha', 29 | 'karma-coverage', 30 | 'karma-phantomjs-launcher', 31 | 'karma-chrome-launcher', 32 | 'karma-safari-launcher', 33 | 'karma-firefox-launcher' 34 | ], 35 | 36 | // frameworks to use 37 | frameworks: ['mocha'], 38 | 39 | // list of files / patterns to load in the browser 40 | files: files, 41 | // proxies: { 42 | // '/base/dist/images/': 'dist/images/' 43 | // }, 44 | exclude: [], 45 | 46 | // Rollup the ES6 Leaflet.markercluster sources into just one file, before tests 47 | preprocessors: { 48 | 'src/index.js': ['rollup'] 49 | }, 50 | rollupPreprocessor: { 51 | plugins: [ 52 | json() 53 | ], 54 | output: { 55 | format: 'umd', 56 | name: 'Leaflet.markercluster' 57 | }, 58 | }, 59 | 60 | // test results reporter to use 61 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 62 | reporters: ['dots'], 63 | 64 | // web server port 65 | port: 9876, 66 | 67 | // level of logging 68 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 69 | logLevel: config.LOG_WARN, 70 | 71 | // enable / disable colors in the output (reporters and logs) 72 | colors: true, 73 | 74 | // enable / disable watching file and executing tests whenever any file changes 75 | autoWatch: false, 76 | 77 | // Start these browsers, currently available: 78 | // - Chrome 79 | // - ChromeCanary 80 | // - Firefox 81 | // - Opera 82 | // - Safari (only Mac) 83 | // - PhantomJS 84 | // - IE (only Windows) 85 | browsers: ['PhantomJS'], 86 | 87 | // If browser does not capture in given timeout [ms], kill it 88 | captureTimeout: 5000, 89 | 90 | // Workaround for PhantomJS random DISCONNECTED error 91 | browserDisconnectTimeout: 10000, // default 2000 92 | browserDisconnectTolerance: 1, // default 0 93 | 94 | // Continuous Integration mode 95 | // if true, it capture browsers, run tests and exit 96 | singleRun: true 97 | }); 98 | }; 99 | -------------------------------------------------------------------------------- /spec/suites/AddLayer.MultipleSpec.js: -------------------------------------------------------------------------------- 1 | describe('addLayer adding multiple markers', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div, clock; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { maxZoom: 18, trackResize: false }); 16 | 17 | map.fitBounds(new L.LatLngBounds([ 18 | [1, 1], 19 | [2, 2] 20 | ])); 21 | }); 22 | 23 | afterEach(function () { 24 | map.remove(); 25 | document.body.removeChild(div); 26 | clock.restore(); 27 | 28 | map = div = clock = null; 29 | }); 30 | 31 | ///////////////////////////// 32 | // TESTS 33 | ///////////////////////////// 34 | it('creates a cluster when 2 overlapping markers are added before the group is added to the map', function () { 35 | 36 | var group = new L.MarkerClusterGroup(); 37 | var marker = new L.Marker([1.5, 1.5]); 38 | var marker2 = new L.Marker([1.5, 1.5]); 39 | 40 | group.addLayer(marker); 41 | group.addLayer(marker2); 42 | map.addLayer(group); 43 | 44 | expect(marker._icon).to.be(undefined); 45 | expect(marker2._icon).to.be(undefined); 46 | 47 | expect(map._panes.markerPane.childNodes.length).to.be(1); 48 | }); 49 | it('creates a cluster when 2 overlapping markers are added after the group is added to the map', function () { 50 | 51 | var group = new L.MarkerClusterGroup(); 52 | var marker = new L.Marker([1.5, 1.5]); 53 | var marker2 = new L.Marker([1.5, 1.5]); 54 | 55 | map.addLayer(group); 56 | group.addLayer(marker); 57 | group.addLayer(marker2); 58 | 59 | expect(marker._icon).to.be(null); //Null as was added and then removed 60 | expect(marker2._icon).to.be(undefined); 61 | 62 | expect(map._panes.markerPane.childNodes.length).to.be(1); 63 | }); 64 | it('creates a cluster with an animation when 2 overlapping markers are added after the group is added to the map', function () { 65 | 66 | var group = new L.MarkerClusterGroup({ animateAddingMarkers: true }); 67 | var marker = new L.Marker([1.5, 1.5]); 68 | var marker2 = new L.Marker([1.5, 1.5]); 69 | 70 | map.addLayer(group); 71 | group.addLayer(marker); 72 | group.addLayer(marker2); 73 | 74 | expect(marker._icon.parentNode).to.be(map._panes.markerPane); 75 | expect(marker2._icon.parentNode).to.be(map._panes.markerPane); 76 | 77 | expect(map._panes.markerPane.childNodes.length).to.be(3); 78 | 79 | //Run the the animation 80 | clock.tick(1000); 81 | 82 | //Then markers should be removed from map 83 | expect(marker._icon).to.be(null); 84 | expect(marker2._icon).to.be(null); 85 | 86 | expect(map._panes.markerPane.childNodes.length).to.be(1); 87 | }); 88 | 89 | it('creates a cluster and marker when 2 overlapping markers and one non-overlapping are added before the group is added to the map', function () { 90 | 91 | var group = new L.MarkerClusterGroup(); 92 | var marker = new L.Marker([1.5, 1.5]); 93 | var marker2 = new L.Marker([1.5, 1.5]); 94 | var marker3 = new L.Marker([3.0, 1.5]); 95 | 96 | group.addLayer(marker); 97 | group.addLayer(marker2); 98 | group.addLayer(marker3); 99 | map.addLayer(group); 100 | 101 | expect(marker._icon).to.be(undefined); 102 | expect(marker2._icon).to.be(undefined); 103 | expect(marker3._icon.parentNode).to.be(map._panes.markerPane); 104 | 105 | expect(map._panes.markerPane.childNodes.length).to.be(2); 106 | }); 107 | it('creates a cluster and marker when 2 overlapping markers and one non-overlapping are added after the group is added to the map', function () { 108 | 109 | var group = new L.MarkerClusterGroup(); 110 | var marker = new L.Marker([1.5, 1.5]); 111 | var marker2 = new L.Marker([1.5, 1.5]); 112 | var marker3 = new L.Marker([3.0, 1.5]); 113 | 114 | map.addLayer(group); 115 | group.addLayer(marker); 116 | group.addLayer(marker2); 117 | group.addLayer(marker3); 118 | 119 | expect(marker._icon).to.be(null); //Null as was added and then removed 120 | expect(marker2._icon).to.be(undefined); 121 | expect(marker3._icon.parentNode).to.be(map._panes.markerPane); 122 | 123 | expect(map._panes.markerPane.childNodes.length).to.be(2); 124 | }); 125 | 126 | it('unspiderfies before adding a new Marker', function () { 127 | 128 | group = new L.MarkerClusterGroup(); 129 | 130 | var marker = new L.Marker([1.5, 1.5]); 131 | var marker2 = new L.Marker([1.5, 1.5]); 132 | var marker3 = new L.Marker([1.5, 1.5]); 133 | 134 | group.addLayers([marker, marker2]); 135 | map.addLayer(group); 136 | 137 | expect(marker._icon).to.be(undefined); 138 | expect(marker2._icon).to.be(undefined); 139 | 140 | group.zoomToShowLayer(marker); 141 | //Run the the animation 142 | clock.tick(1000); 143 | 144 | expect(marker._icon).to.not.be(undefined); 145 | expect(marker._icon).to.not.be(null); 146 | expect(marker2._icon).to.not.be(undefined); 147 | expect(marker2._icon).to.not.be(null); 148 | 149 | group.addLayer(marker3); 150 | //Run the the animation 151 | clock.tick(1000); 152 | 153 | expect(marker._icon).to.be(null); 154 | expect(marker2._icon).to.be(null); 155 | expect(marker3._icon).to.be(undefined); 156 | expect(marker3.__parent._icon).to.not.be(undefined); 157 | expect(marker3.__parent._icon).to.not.be(null); 158 | expect(marker3.__parent._icon.innerText.trim()).to.equal('3'); 159 | }); 160 | }); -------------------------------------------------------------------------------- /spec/suites/AddLayer.SingleSpec.js: -------------------------------------------------------------------------------- 1 | describe('addLayer adding a single marker', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | // Corresponds to zoom level 8 for the above div dimensions. 16 | map.fitBounds(new L.LatLngBounds([ 17 | [1, 1], 18 | [2, 2] 19 | ])); 20 | }); 21 | 22 | afterEach(function () { 23 | if (group instanceof L.MarkerClusterGroup) { 24 | group.clearLayers(); 25 | map.removeLayer(group); 26 | } 27 | 28 | map.remove(); 29 | div.remove() 30 | 31 | div = map = group = null; 32 | }); 33 | 34 | ///////////////////////////// 35 | // TESTS 36 | ///////////////////////////// 37 | it('appears when added to the group before the group is added to the map', function () { 38 | 39 | group = new L.MarkerClusterGroup(); 40 | 41 | var marker = new L.Marker([1.5, 1.5]); 42 | 43 | group.addLayer(marker); 44 | map.addLayer(group); 45 | 46 | expect(marker._icon).to.not.be(undefined); 47 | expect(marker._icon.parentNode).to.be(map._panes.markerPane); 48 | }); 49 | 50 | it('appears when added to the group after the group is added to the map', function () { 51 | 52 | group = new L.MarkerClusterGroup(); 53 | 54 | var marker = new L.Marker([1.5, 1.5]); 55 | 56 | map.addLayer(group); 57 | group.addLayer(marker); 58 | 59 | expect(marker._icon).to.not.be(undefined); 60 | expect(marker._icon.parentNode).to.be(map._panes.markerPane); 61 | }); 62 | 63 | it('appears (using animations) when added after the group is added to the map', function () { 64 | 65 | group = new L.MarkerClusterGroup({ animateAddingMarkers: true }); 66 | 67 | var marker = new L.Marker([1.5, 1.5]); 68 | 69 | map.addLayer(group); 70 | group.addLayer(marker); 71 | 72 | expect(marker._icon).to.not.be(undefined); 73 | expect(marker._icon.parentNode).to.be(map._panes.markerPane); 74 | }); 75 | 76 | it('does not appear when too far away when added before the group is added to the map', function () { 77 | 78 | group = new L.MarkerClusterGroup(); 79 | 80 | var marker = new L.Marker([3.5, 1.5]); 81 | 82 | group.addLayer(marker); 83 | map.addLayer(group); 84 | 85 | expect(marker._icon).to.be(undefined); 86 | }); 87 | 88 | it('does not appear when too far away when added after the group is added to the map', function () { 89 | 90 | group = new L.MarkerClusterGroup(); 91 | 92 | var marker = new L.Marker([3.5, 1.5]); 93 | 94 | map.addLayer(group); 95 | group.addLayer(marker); 96 | 97 | expect(marker._icon).to.be(undefined); 98 | }); 99 | 100 | it('passes control to addLayers when marker is a Layer Group', function () { 101 | 102 | group = new L.MarkerClusterGroup(); 103 | 104 | var marker1 = new L.Marker([1.5, 1.5]); 105 | var marker2 = new L.Marker([1.5, 1.5]); 106 | var layerGroup = new L.LayerGroup([marker1, marker2]); 107 | 108 | map.addLayer(group); 109 | group.addLayer(layerGroup); 110 | 111 | expect(group._topClusterLevel.getChildCount()).to.equal(2); 112 | 113 | expect(marker1._icon).to.be(undefined); 114 | expect(marker2._icon).to.be(undefined); 115 | 116 | expect(map._panes.markerPane.childNodes.length).to.be(1); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /spec/suites/AddLayersSpec.js: -------------------------------------------------------------------------------- 1 | describe('addLayers adding multiple markers', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | // Corresponds to zoom level 8 for the above div dimensions. 16 | map.fitBounds(new L.LatLngBounds([ 17 | [1, 1], 18 | [2, 2] 19 | ])); 20 | }); 21 | 22 | afterEach(function () { 23 | if (group instanceof L.MarkerClusterGroup) { 24 | group.clearLayers(); 25 | map.removeLayer(group); 26 | } 27 | 28 | map.remove(); 29 | div.remove() 30 | 31 | div = map = group = null; 32 | }); 33 | 34 | ///////////////////////////// 35 | // TESTS 36 | ///////////////////////////// 37 | it('creates a cluster when 2 overlapping markers are added before the group is added to the map', function () { 38 | 39 | group = new L.MarkerClusterGroup(); 40 | 41 | var marker = new L.Marker([1.5, 1.5]); 42 | var marker2 = new L.Marker([1.5, 1.5]); 43 | 44 | group.addLayers([marker, marker2]); 45 | map.addLayer(group); 46 | 47 | expect(marker._icon).to.be(undefined); 48 | expect(marker2._icon).to.be(undefined); 49 | 50 | expect(map._panes.markerPane.childNodes.length).to.be(1); 51 | }); 52 | 53 | it('creates a cluster when 2 overlapping markers are added after the group is added to the map', function () { 54 | 55 | group = new L.MarkerClusterGroup(); 56 | 57 | var marker = new L.Marker([1.5, 1.5]); 58 | var marker2 = new L.Marker([1.5, 1.5]); 59 | 60 | map.addLayer(group); 61 | group.addLayers([marker, marker2]); 62 | 63 | expect(marker._icon).to.be(undefined); 64 | expect(marker2._icon).to.be(undefined); 65 | 66 | expect(map._panes.markerPane.childNodes.length).to.be(1); 67 | }); 68 | 69 | it('creates a cluster and marker when 2 overlapping markers and one non-overlapping are added before the group is added to the map', function () { 70 | 71 | group = new L.MarkerClusterGroup(); 72 | 73 | var marker = new L.Marker([1.5, 1.5]); 74 | var marker2 = new L.Marker([1.5, 1.5]); 75 | var marker3 = new L.Marker([3.0, 1.5]); 76 | 77 | group.addLayers([marker, marker2, marker3]); 78 | map.addLayer(group); 79 | 80 | expect(marker._icon).to.be(undefined); 81 | expect(marker2._icon).to.be(undefined); 82 | expect(marker3._icon.parentNode).to.be(map._panes.markerPane); 83 | 84 | expect(map._panes.markerPane.childNodes.length).to.be(2); 85 | }); 86 | 87 | it('creates a cluster and marker when 2 overlapping markers and one non-overlapping are added after the group is added to the map', function () { 88 | 89 | group = new L.MarkerClusterGroup(); 90 | 91 | var marker = new L.Marker([1.5, 1.5]); 92 | var marker2 = new L.Marker([1.5, 1.5]); 93 | var marker3 = new L.Marker([3.0, 1.5]); 94 | 95 | map.addLayer(group); 96 | group.addLayers([marker, marker2, marker3]); 97 | 98 | expect(marker._icon).to.be(undefined); 99 | expect(marker2._icon).to.be(undefined); 100 | expect(marker3._icon.parentNode).to.be(map._panes.markerPane); 101 | 102 | expect(map._panes.markerPane.childNodes.length).to.be(2); 103 | }); 104 | 105 | it('handles nested Layer Groups', function () { 106 | 107 | group = new L.MarkerClusterGroup(); 108 | 109 | var marker1 = new L.Marker([1.5, 1.5]); 110 | var marker2 = new L.Marker([1.5, 1.5]); 111 | var marker3 = new L.Marker([3.0, 1.5]); 112 | var layerGroup = new L.LayerGroup([marker1, new L.LayerGroup([marker2])]); 113 | 114 | map.addLayer(group); 115 | group.addLayers([layerGroup, marker3]); 116 | 117 | expect(marker1._icon).to.be(undefined); 118 | expect(marker2._icon).to.be(undefined); 119 | expect(marker3._icon.parentNode).to.be(map._panes.markerPane); 120 | 121 | expect(map._panes.markerPane.childNodes.length).to.be(2); 122 | }); 123 | 124 | it('unspiderfies before adding new Marker(s)', function () { 125 | 126 | var clock = sinon.useFakeTimers(); 127 | 128 | group = new L.MarkerClusterGroup(); 129 | 130 | var marker = new L.Marker([1.5, 1.5]); 131 | var marker2 = new L.Marker([1.5, 1.5]); 132 | var marker3 = new L.Marker([1.5, 1.5]); 133 | 134 | group.addLayers([marker, marker2]); 135 | map.addLayer(group); 136 | 137 | expect(marker._icon).to.be(undefined); 138 | expect(marker2._icon).to.be(undefined); 139 | 140 | group.zoomToShowLayer(marker); 141 | //Run the the animation 142 | clock.tick(1000); 143 | 144 | expect(marker._icon).to.not.be(undefined); 145 | expect(marker._icon).to.not.be(null); 146 | expect(marker2._icon).to.not.be(undefined); 147 | expect(marker2._icon).to.not.be(null); 148 | 149 | group.addLayers([marker3]); 150 | //Run the the animation 151 | clock.tick(1000); 152 | 153 | expect(marker._icon).to.be(null); 154 | expect(marker2._icon).to.be(null); 155 | expect(marker3._icon).to.be(undefined); 156 | expect(marker3.__parent._icon).to.not.be(undefined); 157 | expect(marker3.__parent._icon).to.not.be(null); 158 | expect(marker3.__parent._icon.innerText.trim()).to.equal('3'); 159 | 160 | clock.restore(); 161 | clock = null; 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /spec/suites/ChildChangingIconSupportSpec.js: -------------------------------------------------------------------------------- 1 | describe('support child markers changing icon', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div, clock; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { maxZoom: 18, trackResize: false }); 16 | 17 | map.fitBounds(new L.LatLngBounds([ 18 | [1, 1], 19 | [2, 2] 20 | ])); 21 | }); 22 | 23 | afterEach(function () { 24 | map.remove(); 25 | document.body.removeChild(div); 26 | clock.restore(); 27 | 28 | map = div = clock = null; 29 | }); 30 | 31 | ///////////////////////////// 32 | // TESTS 33 | ///////////////////////////// 34 | it('child markers end up with the right icon after becoming unclustered', function () { 35 | 36 | var group = new L.MarkerClusterGroup(); 37 | var marker = new L.Marker([1.5, 1.5], { icon: new L.DivIcon({html: 'Inner1Text' }) }); 38 | var marker2 = new L.Marker([1.5, 1.5]); 39 | 40 | map.addLayer(group); 41 | group.addLayer(marker); 42 | 43 | expect(marker._icon.parentNode).to.be(map._panes.markerPane); 44 | expect(marker._icon.innerHTML).to.contain('Inner1Text'); 45 | 46 | group.addLayer(marker2); 47 | 48 | expect(marker._icon).to.be(null); //Have been removed from the map 49 | 50 | marker.setIcon(new L.DivIcon({ html: 'Inner2Text' })); //Change the icon 51 | 52 | group.removeLayer(marker2); //Remove the other marker, so we'll become unclustered 53 | 54 | expect(marker._icon.innerHTML).to.contain('Inner2Text'); 55 | }); 56 | }); -------------------------------------------------------------------------------- /spec/suites/CircleMarkerSupportSpec.js: -------------------------------------------------------------------------------- 1 | describe('support for CircleMarker elements', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var clock, div, map, group; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { maxZoom: 18, trackResize: false }); 16 | 17 | // Corresponds to zoom level 8 for the above div dimensions. 18 | map.fitBounds(new L.LatLngBounds([ 19 | [1, 1], 20 | [2, 2] 21 | ])); 22 | }); 23 | 24 | afterEach(function () { 25 | if (group instanceof L.MarkerClusterGroup) { 26 | group.clearLayers(); 27 | map.removeLayer(group); 28 | } 29 | 30 | map.remove(); 31 | div.remove() 32 | clock.restore(); 33 | 34 | clock = div = map = group = null; 35 | }); 36 | 37 | ///////////////////////////// 38 | // TESTS 39 | ///////////////////////////// 40 | it('appears when added to the group before the group is added to the map', function () { 41 | 42 | group = new L.MarkerClusterGroup(); 43 | 44 | var marker = new L.CircleMarker([1.5, 1.5]); 45 | 46 | group.addLayer(marker); 47 | map.addLayer(group); 48 | 49 | // Leaflet 1.0.0 now uses an intermediate L.Renderer. 50 | // marker > _path > _rootGroup (g) > _container (svg) > pane (div) 51 | expect(marker._path.parentNode.parentNode).to.not.be(undefined); 52 | expect(marker._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 53 | 54 | clock.tick(1000); 55 | }); 56 | 57 | it('appears when added to the group after the group is added to the map', function () { 58 | 59 | group = new L.MarkerClusterGroup(); 60 | 61 | var marker = new L.CircleMarker([1.5, 1.5]); 62 | 63 | group.addLayer(marker); 64 | map.addLayer(group); 65 | 66 | expect(marker._path.parentNode.parentNode).to.not.be(undefined); 67 | expect(marker._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 68 | 69 | clock.tick(1000); 70 | }); 71 | 72 | it('appears animated when added to the group after the group is added to the map', function () { 73 | 74 | group = new L.MarkerClusterGroup({ animateAddingMarkers: true }); 75 | 76 | var marker = new L.CircleMarker([1.5, 1.5]); 77 | var marker2 = new L.CircleMarker([1.5, 1.5]); 78 | 79 | map.addLayer(group); 80 | group.addLayer(marker); 81 | group.addLayer(marker2); 82 | 83 | expect(marker._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 84 | expect(marker2._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 85 | 86 | clock.tick(1000); 87 | 88 | expect(marker._path.parentNode).to.be(null); 89 | expect(marker2._path.parentNode).to.be(null); 90 | }); 91 | 92 | it('creates a cluster when 2 overlapping markers are added before the group is added to the map', function () { 93 | 94 | group = new L.MarkerClusterGroup(); 95 | 96 | var marker = new L.CircleMarker([1.5, 1.5]); 97 | var marker2 = new L.CircleMarker([1.5, 1.5]); 98 | 99 | group.addLayers([marker, marker2]); 100 | map.addLayer(group); 101 | 102 | expect(marker._path).to.be(undefined); 103 | expect(marker2._path).to.be(undefined); 104 | 105 | expect(map._panes.markerPane.childNodes.length).to.be(1); 106 | 107 | clock.tick(1000); 108 | }); 109 | 110 | it('creates a cluster when 2 overlapping markers are added after the group is added to the map', function () { 111 | 112 | group = new L.MarkerClusterGroup(); 113 | 114 | var marker = new L.CircleMarker([1.5, 1.5]); 115 | var marker2 = new L.CircleMarker([1.5, 1.5]); 116 | 117 | map.addLayer(group); 118 | group.addLayer(marker); 119 | group.addLayer(marker2); 120 | 121 | expect(marker._path.parentNode).to.be(null); //Removed then re-added, so null 122 | expect(marker2._path).to.be(undefined); 123 | 124 | expect(map._panes.markerPane.childNodes.length).to.be(1); 125 | 126 | clock.tick(1000); 127 | }); 128 | 129 | it('disappears when removed from the group', function () { 130 | 131 | group = new L.MarkerClusterGroup(); 132 | 133 | var marker = new L.CircleMarker([1.5, 1.5]); 134 | 135 | group.addLayer(marker); 136 | map.addLayer(group); 137 | 138 | expect(marker._path.parentNode).to.not.be(undefined); 139 | expect(marker._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 140 | 141 | group.removeLayer(marker); 142 | 143 | expect(marker._path.parentNode).to.be(null); 144 | 145 | clock.tick(1000); 146 | }); 147 | }); -------------------------------------------------------------------------------- /spec/suites/CircleSupportSpec.js: -------------------------------------------------------------------------------- 1 | describe('support for Circle elements', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var clock, div, map, group; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { maxZoom: 18, trackResize: false }); 16 | 17 | // Corresponds to zoom level 8 for the above div dimensions. 18 | map.fitBounds(new L.LatLngBounds([ 19 | [1, 1], 20 | [2, 2] 21 | ])); 22 | }); 23 | 24 | afterEach(function () { 25 | if (group instanceof L.MarkerClusterGroup) { 26 | group.clearLayers(); 27 | map.removeLayer(group); 28 | } 29 | 30 | map.remove(); 31 | div.remove() 32 | clock.restore(); 33 | 34 | clock = div = map = group = null; 35 | }); 36 | 37 | ///////////////////////////// 38 | // TESTS 39 | ///////////////////////////// 40 | it('appears when added to the group before the group is added to the map', function () { 41 | 42 | group = new L.MarkerClusterGroup(); 43 | 44 | var marker = new L.Circle([1.5, 1.5], 200); 45 | 46 | group.addLayer(marker); 47 | map.addLayer(group); 48 | 49 | // Leaflet 1.0.0 now uses an intermediate L.Renderer. 50 | // marker > _path > _rootGroup (g) > _container (svg) > pane (div) 51 | expect(marker._path.parentNode).to.not.be(undefined); 52 | expect(marker._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 53 | 54 | clock.tick(1000); 55 | }); 56 | 57 | it('appears when added to the group after the group is added to the map', function () { 58 | 59 | group = new L.MarkerClusterGroup(); 60 | 61 | var marker = new L.Circle([1.5, 1.5], 200); 62 | 63 | group.addLayer(marker); 64 | map.addLayer(group); 65 | 66 | expect(marker._path.parentNode).to.not.be(undefined); 67 | expect(marker._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 68 | 69 | clock.tick(1000); 70 | }); 71 | 72 | it('appears animated when added to the group after the group is added to the map', function () { 73 | 74 | group = new L.MarkerClusterGroup({ animateAddingMarkers: true }); 75 | 76 | var marker = new L.Circle([1.5, 1.5], 200); 77 | var marker2 = new L.Circle([1.5, 1.5], 200); 78 | 79 | map.addLayer(group); 80 | group.addLayer(marker); 81 | group.addLayer(marker2); 82 | 83 | expect(marker._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 84 | expect(marker2._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 85 | 86 | clock.tick(1000); 87 | }); 88 | 89 | it('creates a cluster when 2 overlapping markers are added before the group is added to the map', function () { 90 | 91 | group = new L.MarkerClusterGroup(); 92 | 93 | var marker = new L.Circle([1.5, 1.5], 200); 94 | var marker2 = new L.Circle([1.5, 1.5], 200); 95 | 96 | group.addLayers([marker, marker2]); 97 | map.addLayer(group); 98 | 99 | expect(marker._path).to.be(undefined); 100 | expect(marker2._path).to.be(undefined); 101 | 102 | expect(map._panes.markerPane.childNodes.length).to.be(1); 103 | 104 | clock.tick(1000); 105 | }); 106 | 107 | it('creates a cluster when 2 overlapping markers are added after the group is added to the map', function () { 108 | 109 | group = new L.MarkerClusterGroup(); 110 | 111 | var marker = new L.Circle([1.5, 1.5], 200); 112 | var marker2 = new L.Circle([1.5, 1.5], 200); 113 | 114 | map.addLayer(group); 115 | group.addLayer(marker); 116 | group.addLayer(marker2); 117 | 118 | expect(marker._path.parentNode).to.be(null); //Removed then re-added, so null 119 | expect(marker2._path).to.be(undefined); 120 | 121 | expect(map._panes.markerPane.childNodes.length).to.be(1); 122 | 123 | clock.tick(1000); 124 | }); 125 | 126 | it('disappears when removed from the group', function () { 127 | 128 | group = new L.MarkerClusterGroup(); 129 | 130 | var marker = new L.Circle([1.5, 1.5], 200); 131 | 132 | group.addLayer(marker); 133 | map.addLayer(group); 134 | 135 | expect(marker._path.parentNode).to.not.be(undefined); 136 | expect(marker._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 137 | 138 | group.removeLayer(marker); 139 | 140 | expect(marker._path.parentNode).to.be(null); 141 | 142 | clock.tick(1000); 143 | }); 144 | }); -------------------------------------------------------------------------------- /spec/suites/DistanceGridSpec.js: -------------------------------------------------------------------------------- 1 | describe('distance grid', function () { 2 | it('addObject', function () { 3 | var grid = new L.DistanceGrid(100), 4 | obj = {}; 5 | 6 | expect(grid.addObject(obj, { x: 0, y: 0 })).to.eql(undefined); 7 | expect(grid.removeObject(obj, { x: 0, y: 0 })).to.eql(true); 8 | }); 9 | 10 | it('eachObject', function (done) { 11 | var grid = new L.DistanceGrid(100), 12 | obj = {}; 13 | 14 | expect(grid.addObject(obj, { x: 0, y: 0 })).to.eql(undefined); 15 | 16 | grid.eachObject(function(o) { 17 | expect(o).to.eql(obj); 18 | done(); 19 | }); 20 | }); 21 | 22 | it('getNearObject', function () { 23 | var grid = new L.DistanceGrid(100), 24 | obj = {}; 25 | 26 | grid.addObject(obj, { x: 0, y: 0 }); 27 | 28 | expect(grid.getNearObject({ x: 50, y: 50 })).to.equal(obj); 29 | expect(grid.getNearObject({ x: 100, y: 0 })).to.equal(obj); 30 | }); 31 | 32 | it('getNearObject with cellSize 0', function () { 33 | var grid = new L.DistanceGrid(0), 34 | obj = {}; 35 | 36 | grid.addObject(obj, { x: 0, y: 0 }); 37 | 38 | expect(grid.getNearObject({ x: 50, y: 50 })).to.equal(null); 39 | expect(grid.getNearObject({ x: 0, y: 0 })).to.equal(obj); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /spec/suites/LeafletSpec.js: -------------------------------------------------------------------------------- 1 | describe('L#noConflict', function() { 2 | it('restores the previous L value and returns Leaflet namespace', function(){ 3 | 4 | expect(L.version).to.be.ok(); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /spec/suites/NonPointSpec.js: -------------------------------------------------------------------------------- 1 | describe('adding non point data works', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | // Corresponds to zoom level 8 for the above div dimensions. 16 | map.fitBounds(new L.LatLngBounds([ 17 | [1, 1], 18 | [2, 2] 19 | ])); 20 | }); 21 | 22 | afterEach(function () { 23 | if (group instanceof L.MarkerClusterGroup) { 24 | group.clearLayers(); 25 | map.removeLayer(group); 26 | } 27 | 28 | map.remove(); 29 | div.remove() 30 | 31 | div = map = group = null; 32 | }); 33 | 34 | 35 | ///////////////////////////// 36 | // TESTS 37 | ///////////////////////////// 38 | it('Allows adding a polygon before via addLayer', function () { 39 | 40 | group = new L.MarkerClusterGroup(); 41 | 42 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0,2.0], [1.5, 2.0]]); 43 | 44 | group.addLayer(polygon); 45 | map.addLayer(group); 46 | 47 | // Leaflet 1.0.0 now uses an intermediate L.Renderer. 48 | // polygon > _path > _rootGroup (g) > _container (svg) > pane (div) 49 | expect(polygon._path).to.not.be(undefined); 50 | expect(polygon._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 51 | 52 | expect(group.hasLayer(polygon)); 53 | }); 54 | 55 | it('Allows adding a polygon before via addLayers([])', function () { 56 | 57 | group = new L.MarkerClusterGroup(); 58 | 59 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 60 | 61 | group.addLayers([polygon]); 62 | map.addLayer(group); 63 | 64 | expect(polygon._path).to.not.be(undefined); 65 | expect(polygon._path.parentNode.parentNode.parentNode).to.be(map.getPane('overlayPane')); 66 | }); 67 | 68 | it('Removes polygons from map when removed', function () { 69 | 70 | group = new L.MarkerClusterGroup(); 71 | 72 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 73 | 74 | group.addLayer(polygon); 75 | map.addLayer(group); 76 | map.removeLayer(group); 77 | 78 | expect(polygon._path.parentNode).to.be(null); 79 | }); 80 | 81 | describe('hasLayer', function () { 82 | 83 | it('returns false when not added', function () { 84 | group = new L.MarkerClusterGroup(); 85 | 86 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 87 | 88 | expect(group.hasLayer(polygon)).to.be(false); 89 | 90 | map.addLayer(group); 91 | 92 | expect(group.hasLayer(polygon)).to.be(false); 93 | 94 | map.addLayer(polygon); 95 | 96 | expect(group.hasLayer(polygon)).to.be(false); 97 | }); 98 | 99 | it('returns true before adding to map', function() { 100 | group = new L.MarkerClusterGroup(); 101 | 102 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 103 | 104 | group.addLayers([polygon]); 105 | 106 | expect(group.hasLayer(polygon)).to.be(true); 107 | }); 108 | 109 | it('returns true after adding to map after adding polygon', function () { 110 | group = new L.MarkerClusterGroup(); 111 | 112 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 113 | 114 | group.addLayer(polygon); 115 | map.addLayer(group); 116 | 117 | expect(group.hasLayer(polygon)).to.be(true); 118 | }); 119 | 120 | it('returns true after adding to map before adding polygon', function () { 121 | group = new L.MarkerClusterGroup(); 122 | 123 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 124 | 125 | map.addLayer(group); 126 | group.addLayer(polygon); 127 | 128 | expect(group.hasLayer(polygon)).to.be(true); 129 | }); 130 | 131 | }); 132 | 133 | describe('removeLayer', function() { 134 | 135 | it('removes before adding to map', function () { 136 | group = new L.MarkerClusterGroup(); 137 | 138 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 139 | 140 | group.addLayer(polygon); 141 | expect(group.hasLayer(polygon)).to.be(true); 142 | 143 | group.removeLayer(polygon); 144 | expect(group.hasLayer(polygon)).to.be(false); 145 | }); 146 | 147 | it('removes before adding to map', function () { 148 | group = new L.MarkerClusterGroup(); 149 | 150 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 151 | 152 | group.addLayers([polygon]); 153 | expect(group.hasLayer(polygon)).to.be(true); 154 | 155 | group.removeLayer(polygon); 156 | expect(group.hasLayer(polygon)).to.be(false); 157 | }); 158 | 159 | it('removes after adding to map after adding polygon', function () { 160 | group = new L.MarkerClusterGroup(); 161 | 162 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 163 | 164 | group.addLayer(polygon); 165 | map.addLayer(group); 166 | expect(group.hasLayer(polygon)).to.be(true); 167 | 168 | group.removeLayer(polygon); 169 | expect(group.hasLayer(polygon)).to.be(false); 170 | }); 171 | 172 | it('removes after adding to map before adding polygon', function () { 173 | group = new L.MarkerClusterGroup(); 174 | 175 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 176 | 177 | map.addLayer(group); 178 | group.addLayer(polygon); 179 | expect(group.hasLayer(polygon)).to.be(true); 180 | 181 | group.removeLayer(polygon); 182 | expect(group.hasLayer(polygon)).to.be(false); 183 | }); 184 | 185 | }); 186 | 187 | describe('removeLayers', function () { 188 | 189 | it('removes before adding to map', function () { 190 | group = new L.MarkerClusterGroup(); 191 | 192 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 193 | 194 | group.addLayer(polygon); 195 | expect(group.hasLayer(polygon)).to.be(true); 196 | 197 | group.removeLayers([polygon]); 198 | expect(group.hasLayer(polygon)).to.be(false); 199 | }); 200 | 201 | it('removes before adding to map', function () { 202 | group = new L.MarkerClusterGroup(); 203 | 204 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 205 | 206 | group.addLayers([polygon]); 207 | expect(group.hasLayer(polygon)).to.be(true); 208 | 209 | group.removeLayers([polygon]); 210 | expect(group.hasLayer(polygon)).to.be(false); 211 | }); 212 | 213 | it('removes after adding to map after adding polygon', function () { 214 | group = new L.MarkerClusterGroup(); 215 | 216 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 217 | 218 | group.addLayer(polygon); 219 | map.addLayer(group); 220 | expect(group.hasLayer(polygon)).to.be(true); 221 | 222 | group.removeLayers([polygon]); 223 | expect(group.hasLayer(polygon)).to.be(false); 224 | }); 225 | 226 | it('removes after adding to map before adding polygon', function () { 227 | group = new L.MarkerClusterGroup(); 228 | 229 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 230 | 231 | map.addLayer(group); 232 | group.addLayer(polygon); 233 | expect(group.hasLayer(polygon)).to.be(true); 234 | 235 | group.removeLayers([polygon]); 236 | expect(group.hasLayer(polygon)).to.be(false); 237 | }); 238 | 239 | }); 240 | }); -------------------------------------------------------------------------------- /spec/suites/PaneSpec.js: -------------------------------------------------------------------------------- 1 | describe('Map pane selection', function() { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | // Create map pane 16 | map.createPane('testPane'); 17 | 18 | // Corresponds to zoom level 8 for the above div dimensions. 19 | map.fitBounds(new L.LatLngBounds([ 20 | [1, 1], 21 | [2, 2] 22 | ])); 23 | }); 24 | 25 | afterEach(function () { 26 | if (group instanceof L.MarkerClusterGroup) { 27 | group.clearLayers(); 28 | map.removeLayer(group); 29 | } 30 | 31 | map.remove(); 32 | div.remove() 33 | 34 | div = map = group = null; 35 | }); 36 | 37 | ///////////////////////////// 38 | // TESTS 39 | ///////////////////////////// 40 | it('recognizes and applies option', function() { 41 | group = new L.MarkerClusterGroup({clusterPane: 'testPane'}); 42 | 43 | var marker = new L.Marker([1.5, 1.5]); 44 | var marker2 = new L.Marker([1.5, 1.5]); 45 | 46 | group.addLayers([marker, marker2]); 47 | map.addLayer(group); 48 | 49 | expect(map._panes.testPane.childNodes.length).to.be(1); 50 | }); 51 | 52 | it('defaults to default marker pane', function() { 53 | group = new L.MarkerClusterGroup(); 54 | 55 | var marker = new L.Marker([1.5, 1.5]); 56 | var marker2 = new L.Marker([1.5, 1.5]); 57 | 58 | group.addLayers([marker, marker2]); 59 | map.addLayer(group); 60 | 61 | expect(map._panes[L.Marker.prototype.options.pane].childNodes.length).to.be(1); 62 | }); 63 | }); -------------------------------------------------------------------------------- /spec/suites/QuickHullSpec.js: -------------------------------------------------------------------------------- 1 | describe('quickhull', function () { 2 | describe('getDistant', function () { 3 | it('zero distance', function () { 4 | var bl = [ 5 | { lat: 0, lng: 0 }, 6 | { lat: 0, lng: 10 } 7 | ]; 8 | expect(L.QuickHull.getDistant({ lat: 0, lng: 0 }, bl)).to.eql(0); 9 | }); 10 | it('non-zero distance', function () { 11 | var bl = [ 12 | { lat: 0, lng: 0 }, 13 | { lat: 0, lng: 10 } 14 | ]; 15 | expect(L.QuickHull.getDistant({ lat: 5, lng: 5 }, bl)).to.eql(-50); 16 | }); 17 | }); 18 | 19 | describe('getConvexHull', function () { 20 | it('creates a hull', function () { 21 | expect(L.QuickHull.getConvexHull([ { lat: 0, lng: 0 }, 22 | { lat: 10, lng: 0 }, 23 | { lat: 10, lng: 10 }, 24 | { lat: 0, lng: 10 }, 25 | { lat: 5, lng: 5 } 26 | ])).to.eql([ 27 | { lat: 0, lng: 10 }, 28 | { lat: 10, lng: 10 }, 29 | { lat: 10, lng: 0 }, 30 | { lat: 0, lng: 0 } 31 | ]); 32 | }); 33 | it('creates a hull for vertically-aligned objects', function () { 34 | expect(L.QuickHull.getConvexHull([ { lat: 0, lng: 0 }, 35 | { lat: 5, lng: 0 }, 36 | { lat: 10, lng: 0 } 37 | ])).to.eql([ 38 | { lat: 0, lng: 0 }, 39 | { lat: 10, lng: 0 } 40 | ]); 41 | }); 42 | it('creates a hull for horizontally-aligned objects', function () { 43 | expect(L.QuickHull.getConvexHull([ { lat: 0, lng: 0 }, 44 | { lat: 0, lng: 5 }, 45 | { lat: 0, lng: 10 } 46 | ])).to.eql([ 47 | { lat: 0, lng: 0 }, 48 | { lat: 0, lng: 10 } 49 | ]); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /spec/suites/RememberOpacity.js: -------------------------------------------------------------------------------- 1 | describe('Remember opacity', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div, clock, markers, group; 6 | 7 | var markerDefs = [ 8 | {latLng: [ 0, 0], opts: {opacity: 0.9}}, 9 | {latLng: [ 0, 1], opts: {opacity: 0.5}}, 10 | {latLng: [ 0,-1], opts: {opacity: 0.5}}, 11 | {latLng: [ 1, 0], opts: {opacity: 0.5}}, 12 | {latLng: [-1, 0], opts: {opacity: 0.5}}, 13 | {latLng: [ 1, 1], opts: {opacity: 0.2}}, 14 | {latLng: [ 1,-1], opts: {opacity: 0.2}}, 15 | {latLng: [-1, 1], opts: {opacity: 0.2}}, 16 | {latLng: [-1,-1], opts: {opacity: 0.2}} 17 | ]; 18 | 19 | var bounds = L.latLngBounds( L.latLng( -1.1, -1.1), 20 | L.latLng( 1.1, 1.1) ); 21 | 22 | beforeEach(function () { 23 | clock = sinon.useFakeTimers(); 24 | 25 | div = document.createElement('div'); 26 | div.style.width = '200px'; 27 | div.style.height = '200px'; 28 | document.body.appendChild(div); 29 | 30 | map = L.map(div, { maxZoom: 18, trackResize: false }); 31 | 32 | markers = []; 33 | for (var i = 0; i < markerDefs.length; i++) { 34 | markers.push( L.marker(markerDefs[i].latLng, markerDefs[i].opts ) ); 35 | } 36 | }); 37 | 38 | afterEach(function () { 39 | group.clearLayers(); 40 | map.removeLayer(group); 41 | map.remove(); 42 | document.body.removeChild(div); 43 | clock.restore(); 44 | 45 | clock = div = map = markers = group = null; 46 | }); 47 | 48 | ///////////////////////////// 49 | // TESTS 50 | ///////////////////////////// 51 | it('clusters semitransparent markers into an opaque one', function () { 52 | map.setView(new L.LatLng(0,0), 1); 53 | 54 | group = new L.MarkerClusterGroup({ 55 | maxClusterRadius: 20 56 | }); 57 | group.addLayers(markers); 58 | map.addLayer(group); 59 | 60 | var visibleClusters = group._featureGroup.getLayers(); 61 | expect(visibleClusters.length).to.be(1); 62 | expect(visibleClusters[0].options.opacity).to.be(1); 63 | }); 64 | 65 | 66 | it('unclusters an opaque marker into semitransparent ones', function () { 67 | map.setView(new L.LatLng(0,0), 1); 68 | var visibleClusters; 69 | 70 | group = new L.MarkerClusterGroup({ 71 | maxClusterRadius: 20 72 | }); 73 | group.addLayers(markers); 74 | map.addLayer(group); 75 | 76 | map.fitBounds(bounds); 77 | clock.tick(1000); 78 | 79 | visibleClusters = group._featureGroup.getLayers(); 80 | expect(visibleClusters.length).to.be(9); 81 | for (var i=0; i<9; i++) { 82 | expect(visibleClusters[i].options.opacity).to.be.within(0.2,0.9); 83 | } 84 | 85 | // It shall also work after zooming in/out a second time. 86 | map.setView(new L.LatLng(0,0), 1); 87 | clock.tick(1000); 88 | 89 | map.fitBounds(bounds); 90 | clock.tick(1000); 91 | 92 | visibleClusters = group._featureGroup.getLayers(); 93 | expect(visibleClusters.length).to.be(9); 94 | for (var i=0; i<9; i++) { 95 | expect(visibleClusters[i].options.opacity).to.be.within(0.2,0.9); 96 | } 97 | }); 98 | 99 | 100 | it('has no problems zooming in and out several times', function () { 101 | var visibleClusters; 102 | 103 | group = new L.MarkerClusterGroup({ 104 | maxClusterRadius: 20 105 | }); 106 | group.addLayers(markers); 107 | map.addLayer(group); 108 | 109 | // Zoom in and out a couple times 110 | for (var i=0; i<10; i++) { 111 | map.fitBounds(bounds); 112 | clock.tick(1000); 113 | 114 | visibleClusters = group._featureGroup.getLayers(); 115 | expect(visibleClusters.length).to.be(9); 116 | for (var i=0; i<9; i++) { 117 | expect(visibleClusters[i].options.opacity).to.be.within(0.2,0.9); 118 | } 119 | 120 | map.setView(new L.LatLng(0,0), 1); 121 | clock.tick(1000); 122 | 123 | visibleClusters = group._featureGroup.getLayers(); 124 | expect(visibleClusters.length).to.be(1); 125 | expect(visibleClusters[0].options.opacity).to.be(1); 126 | } 127 | 128 | }); 129 | 130 | it('retains the opacity of each individual marker', function () { 131 | map.setView(new L.LatLng(0,0), 1); 132 | 133 | var visibleClusters; 134 | group = new L.MarkerClusterGroup({ 135 | maxClusterRadius: 20 136 | }); 137 | group.addLayers(markers); 138 | map.addLayer(group); 139 | 140 | 141 | // Zoom in and out a couple times 142 | for (var i=0; i<5; i++) { 143 | map.fitBounds(bounds); 144 | clock.tick(1000); 145 | 146 | map.setView(new L.LatLng(0,0), 1); 147 | clock.tick(1000); 148 | } 149 | 150 | for (var i=0; i>> 0; 14 | if (typeof fun !== "function") 15 | throw new TypeError(); 16 | 17 | var res = new Array(len); 18 | var thisp = arguments[1]; 19 | for (var i = 0; i < len; i++) { 20 | if (i in t) 21 | res[i] = fun.call(thisp, t[i], i, t); 22 | } 23 | 24 | return res; 25 | }; 26 | } 27 | 28 | Number.isFinite = Number.isFinite || function(value) { 29 | return typeof value === 'number' && isFinite(value); 30 | } -------------------------------------------------------------------------------- /spec/suites/animateOptionSpec.js: -------------------------------------------------------------------------------- 1 | describe('animate option', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | // Corresponds to zoom level 8 for the above div dimensions. 16 | map.fitBounds(new L.LatLngBounds([ 17 | [1, 1], 18 | [2, 2] 19 | ])); 20 | }); 21 | 22 | afterEach(function () { 23 | if (group instanceof L.MarkerClusterGroup) { 24 | group.removeLayers(group.getLayers()); 25 | map.removeLayer(group); 26 | } 27 | 28 | map.remove(); 29 | div.remove(); 30 | 31 | div = map = group = null; 32 | }); 33 | 34 | ///////////////////////////// 35 | // TESTS 36 | ///////////////////////////// 37 | it('hooks animated methods version by default', function () { 38 | 39 | // Need to add to map so that we have the top cluster level created. 40 | group = L.markerClusterGroup().addTo(map); 41 | 42 | var withAnimation = L.MarkerClusterGroup.prototype._withAnimation; 43 | 44 | // MCG animated methods. 45 | expect(group._animationStart).to.be(withAnimation._animationStart); 46 | expect(group._animationZoomIn).to.be(withAnimation._animationZoomIn); 47 | expect(group._animationZoomOut).to.be(withAnimation._animationZoomOut); 48 | expect(group._animationAddLayer).to.be(withAnimation._animationAddLayer); 49 | 50 | // MarkerCluster spiderfy animated methods 51 | var cluster = group._topClusterLevel; 52 | 53 | withAnimation = L.MarkerCluster.prototype; 54 | 55 | expect(cluster._animationSpiderfy).to.be(withAnimation._animationSpiderfy); 56 | expect(cluster._animationUnspiderfy).to.be(withAnimation._animationUnspiderfy); 57 | 58 | }); 59 | 60 | it('hooks non-animated methods version when set to false', function () { 61 | 62 | // Need to add to map so that we have the top cluster level created. 63 | group = L.markerClusterGroup({animate: false}).addTo(map); 64 | 65 | var noAnimation = L.MarkerClusterGroup.prototype._noAnimation; 66 | 67 | // MCG non-animated methods. 68 | expect(group._animationStart).to.be(noAnimation._animationStart); 69 | expect(group._animationZoomIn).to.be(noAnimation._animationZoomIn); 70 | expect(group._animationZoomOut).to.be(noAnimation._animationZoomOut); 71 | expect(group._animationAddLayer).to.be(noAnimation._animationAddLayer); 72 | 73 | // MarkerCluster spiderfy non-animated methods 74 | var cluster = group._topClusterLevel; 75 | 76 | noAnimation = L.MarkerClusterNonAnimated.prototype; 77 | 78 | expect(cluster._animationSpiderfy).to.be(noAnimation._animationSpiderfy); 79 | expect(cluster._animationUnspiderfy).to.be(noAnimation._animationUnspiderfy); 80 | 81 | }); 82 | 83 | it('always hooks non-animated methods version when L.DomUtil.TRANSITION is false', function () { 84 | // Fool Leaflet, make it think the browser does not support transitions. 85 | var realDomUtil = L.DomUtil; 86 | var fakeDomUtil = {}; 87 | for (k in realDomUtil) { 88 | fakeDomUtil[k] = realDomUtil[k]; 89 | } 90 | fakeDomUtil.TRANSITION = false; 91 | L.DomUtil = fakeDomUtil; 92 | 93 | try { 94 | // Need to add to map so that we have the top cluster level created. 95 | group = L.markerClusterGroup({animate: true}).addTo(map); 96 | 97 | var noAnimation = L.MarkerClusterGroup.prototype._noAnimation; 98 | 99 | // MCG non-animated methods. 100 | expect(group._animationStart).to.be(noAnimation._animationStart); 101 | expect(group._animationZoomIn).to.be(noAnimation._animationZoomIn); 102 | expect(group._animationZoomOut).to.be(noAnimation._animationZoomOut); 103 | expect(group._animationAddLayer).to.be(noAnimation._animationAddLayer); 104 | 105 | // MarkerCluster spiderfy non-animated methods 106 | var cluster = group._topClusterLevel; 107 | 108 | noAnimation = L.MarkerClusterNonAnimated.prototype; 109 | 110 | expect(cluster._animationSpiderfy).to.be(noAnimation._animationSpiderfy); 111 | expect(cluster._animationUnspiderfy).to.be(noAnimation._animationUnspiderfy); 112 | } finally { 113 | //Undo the DomUtil replacement hack 114 | L.DomUtil = realDomUtil; 115 | } 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /spec/suites/clearLayersSpec.js: -------------------------------------------------------------------------------- 1 | describe('clearLayer', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | map.fitBounds(new L.LatLngBounds([ 16 | [1, 1], 17 | [2, 2] 18 | ])); 19 | }); 20 | afterEach(function () { 21 | map.remove(); 22 | document.body.removeChild(div); 23 | 24 | map = div = null; 25 | }); 26 | 27 | ///////////////////////////// 28 | // TESTS 29 | ///////////////////////////// 30 | it('clears everything before adding to map', function () { 31 | var group = new L.MarkerClusterGroup(); 32 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 33 | var marker = new L.Marker([1.5, 1.5]); 34 | 35 | group.addLayers([polygon, marker]); 36 | group.clearLayers(); 37 | 38 | expect(group.hasLayer(polygon)).to.be(false); 39 | expect(group.hasLayer(marker)).to.be(false); 40 | }); 41 | 42 | it('hits polygons and markers after adding to map', function () { 43 | var group = new L.MarkerClusterGroup(); 44 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 45 | var marker = new L.Marker([1.5, 1.5]); 46 | 47 | group.addLayers([polygon, marker]); 48 | map.addLayer(group); 49 | group.clearLayers(); 50 | 51 | expect(group.hasLayer(polygon)).to.be(false); 52 | expect(group.hasLayer(marker)).to.be(false); 53 | }); 54 | }); -------------------------------------------------------------------------------- /spec/suites/disableClusteringAtZoomSpec.js: -------------------------------------------------------------------------------- 1 | describe('disableClusteringAtZoom option', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group, clock; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { maxZoom: 18, trackResize: false }); 16 | 17 | // Corresponds to zoom level 8 for the above div dimensions. 18 | map.fitBounds(new L.LatLngBounds([ 19 | [1, 1], 20 | [2, 2] 21 | ])); 22 | }); 23 | 24 | afterEach(function () { 25 | group.clearLayers(); 26 | map.removeLayer(group); 27 | map.remove(); 28 | div.remove(); 29 | clock.restore(); 30 | 31 | div, map, group, clock = null; 32 | }); 33 | 34 | ///////////////////////////// 35 | // TESTS 36 | ///////////////////////////// 37 | it('unclusters at zoom level equal or higher', function () { 38 | 39 | var maxZoom = 15; 40 | 41 | group = new L.MarkerClusterGroup({ 42 | disableClusteringAtZoom: maxZoom 43 | }); 44 | 45 | group.addLayers([ 46 | new L.Marker([1.5, 1.5]), 47 | new L.Marker([1.5, 1.5]) 48 | ]); 49 | map.addLayer(group); 50 | 51 | expect(group._maxZoom).to.equal(maxZoom - 1); 52 | 53 | expect(map._panes.markerPane.childNodes.length).to.equal(1); // 1 cluster. 54 | 55 | map.setZoom(14); 56 | clock.tick(1000); 57 | expect(map._panes.markerPane.childNodes.length).to.equal(1); // 1 cluster. 58 | 59 | map.setZoom(15); 60 | clock.tick(1000); 61 | expect(map._panes.markerPane.childNodes.length).to.equal(2); // 2 markers. 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /spec/suites/eachLayerSpec.js: -------------------------------------------------------------------------------- 1 | describe('eachLayer', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | map.fitBounds(new L.LatLngBounds([ 16 | [1, 1], 17 | [2, 2] 18 | ])); 19 | }); 20 | 21 | afterEach(function () { 22 | map.remove(); 23 | document.body.removeChild(div); 24 | 25 | map = div = null; 26 | }); 27 | 28 | ///////////////////////////// 29 | // TESTS 30 | ///////////////////////////// 31 | it('hits polygons and markers before adding to map', function () { 32 | var group = new L.MarkerClusterGroup(); 33 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 34 | var marker = new L.Marker([1.5, 1.5]); 35 | 36 | group.addLayers([polygon, marker]); 37 | 38 | var layers = []; 39 | group.eachLayer(function (l) { 40 | layers.push(l); 41 | }); 42 | 43 | expect(layers.length).to.be(2); 44 | expect(layers).to.contain(marker); 45 | expect(layers).to.contain(polygon); 46 | }); 47 | 48 | it('hits polygons and markers after adding to map', function () { 49 | var group = new L.MarkerClusterGroup(); 50 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 51 | var marker = new L.Marker([1.5, 1.5]); 52 | 53 | group.addLayers([polygon, marker]); 54 | map.addLayer(group); 55 | 56 | var layers = []; 57 | group.eachLayer(function (l) { 58 | layers.push(l); 59 | }); 60 | 61 | expect(layers.length).to.be(2); 62 | expect(layers).to.contain(marker); 63 | expect(layers).to.contain(polygon); 64 | }); 65 | }); -------------------------------------------------------------------------------- /spec/suites/getBoundsSpec.js: -------------------------------------------------------------------------------- 1 | describe('getBounds', function() { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div; 6 | 7 | beforeEach(function() { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | map.fitBounds(new L.LatLngBounds([ 16 | [1, 1], 17 | [2, 2] 18 | ])); 19 | }); 20 | afterEach(function() { 21 | map.remove(); 22 | document.body.removeChild(div); 23 | 24 | map = div = null; 25 | }); 26 | 27 | ///////////////////////////// 28 | // TESTS 29 | ///////////////////////////// 30 | describe('polygon layer', function() { 31 | it('returns the correct bounds before adding to the map', function() { 32 | var group = new L.MarkerClusterGroup(); 33 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 34 | 35 | group.addLayer(polygon); 36 | 37 | expect(group.getBounds().equals(polygon.getBounds())).to.be(true); 38 | }); 39 | 40 | it('returns the correct bounds after adding to the map after adding polygon', function() { 41 | var group = new L.MarkerClusterGroup(); 42 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 43 | 44 | group.addLayer(polygon); 45 | map.addLayer(group); 46 | 47 | expect(group.getBounds().equals(polygon.getBounds())).to.be(true); 48 | }); 49 | 50 | it('returns the correct bounds after adding to the map before adding polygon', function() { 51 | var group = new L.MarkerClusterGroup(); 52 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 53 | 54 | map.addLayer(group); 55 | group.addLayer(polygon); 56 | 57 | expect(group.getBounds().equals(polygon.getBounds())).to.be(true); 58 | }); 59 | }); 60 | 61 | describe('marker layers', function () { 62 | it('returns the correct bounds before adding to the map', function () { 63 | var group = new L.MarkerClusterGroup(); 64 | var marker = new L.Marker([1.5, 1.5]); 65 | var marker2 = new L.Marker([1.0, 5.0]); 66 | var marker3 = new L.Marker([6.0, 2.0]); 67 | 68 | group.addLayers([marker, marker2, marker3]); 69 | 70 | expect(group.getBounds().equals(L.latLngBounds([1.0, 5.0], [6.0, 1.5]))).to.be(true); 71 | }); 72 | 73 | it('returns the correct bounds after adding to the map after adding markers', function () { 74 | var group = new L.MarkerClusterGroup(); 75 | var marker = new L.Marker([1.5, 1.5]); 76 | var marker2 = new L.Marker([1.0, 5.0]); 77 | var marker3 = new L.Marker([6.0, 2.0]); 78 | 79 | group.addLayers([marker, marker2, marker3]); 80 | map.addLayer(group); 81 | 82 | expect(group.getBounds().equals(L.latLngBounds([1.0, 5.0], [6.0, 1.5]))).to.be(true); 83 | }); 84 | 85 | it('returns the correct bounds after adding to the map before adding markers', function () { 86 | var group = new L.MarkerClusterGroup(); 87 | var marker = new L.Marker([1.5, 1.5]); 88 | var marker2 = new L.Marker([1.0, 5.0]); 89 | var marker3 = new L.Marker([6.0, 2.0]); 90 | 91 | map.addLayer(group); 92 | group.addLayers([marker, marker2, marker3]); 93 | 94 | expect(group.getBounds().equals(L.latLngBounds([1.0, 5.0], [6.0, 1.5]))).to.be(true); 95 | }); 96 | }); 97 | 98 | describe('marker and polygon layers', function() { 99 | it('returns the correct bounds before adding to the map', function() { 100 | var group = new L.MarkerClusterGroup(); 101 | var marker = new L.Marker([6.0, 3.0]); 102 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 103 | 104 | group.addLayers([marker, polygon]); 105 | 106 | expect(group.getBounds().equals(L.latLngBounds([1.5, 1.5], [6.0, 3.0]))).to.be(true); 107 | }); 108 | 109 | it('returns the correct bounds after adding to the map', function () { 110 | var group = new L.MarkerClusterGroup(); 111 | var marker = new L.Marker([6.0, 3.0]); 112 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 113 | 114 | map.addLayer(group); 115 | group.addLayers([marker, polygon]); 116 | 117 | expect(group.getBounds().equals(L.latLngBounds([1.5, 1.5], [6.0, 3.0]))).to.be(true); 118 | }); 119 | }); 120 | 121 | describe('blank layer', function () { 122 | it('returns a blank bounds', function () { 123 | var group = new L.MarkerClusterGroup(); 124 | 125 | expect(group.getBounds().isValid()).to.be(false); 126 | }); 127 | }); 128 | }); -------------------------------------------------------------------------------- /spec/suites/getLayersSpec.js: -------------------------------------------------------------------------------- 1 | describe('getLayers', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | map.fitBounds(new L.LatLngBounds([ 16 | [1, 1], 17 | [2, 2] 18 | ])); 19 | }); 20 | afterEach(function () { 21 | map.remove(); 22 | document.body.removeChild(div); 23 | 24 | map = div = null; 25 | }); 26 | 27 | ///////////////////////////// 28 | // TESTS 29 | ///////////////////////////// 30 | it('hits polygons and markers before adding to map', function () { 31 | var group = new L.MarkerClusterGroup(); 32 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 33 | var marker = new L.Marker([1.5, 1.5]); 34 | 35 | group.addLayers([polygon, marker]); 36 | 37 | var layers = group.getLayers(); 38 | 39 | expect(layers.length).to.be(2); 40 | expect(layers).to.contain(marker); 41 | expect(layers).to.contain(polygon); 42 | }); 43 | 44 | it('hits polygons and markers after adding to map', function () { 45 | var group = new L.MarkerClusterGroup(); 46 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 47 | var marker = new L.Marker([1.5, 1.5]); 48 | 49 | group.addLayers([polygon, marker]); 50 | map.addLayer(group); 51 | 52 | var layers = group.getLayers(); 53 | 54 | expect(layers.length).to.be(2); 55 | expect(layers).to.contain(marker); 56 | expect(layers).to.contain(polygon); 57 | }); 58 | 59 | it('skips markers and polygons removed while not on the map', function () { 60 | var group = new L.MarkerClusterGroup(); 61 | var polygon = new L.Polygon([[1.5, 1.5], [2.0, 1.5], [2.0, 2.0], [1.5, 2.0]]); 62 | var marker = new L.Marker([1.5, 1.5]); 63 | 64 | group.addLayers([polygon, marker]); 65 | 66 | map.addLayer(group); 67 | map.removeLayer(group); 68 | 69 | group.removeLayers([polygon, marker]); 70 | 71 | var layers = group.getLayers(); 72 | 73 | expect(layers.length).to.be(0); 74 | }); 75 | }); -------------------------------------------------------------------------------- /spec/suites/getVisibleParentSpec.js: -------------------------------------------------------------------------------- 1 | describe('getVisibleParent', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var group, map, div; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | map.fitBounds(new L.LatLngBounds([ 16 | [1, 1], 17 | [2, 2] 18 | ])); 19 | }); 20 | 21 | afterEach(function () { 22 | group.clearLayers(); 23 | map.removeLayer(group); 24 | map.remove(); 25 | document.body.removeChild(div); 26 | 27 | group = map = div = null; 28 | }); 29 | 30 | ///////////////////////////// 31 | // TESTS 32 | ///////////////////////////// 33 | it('gets the marker if the marker is visible', function () { 34 | group = new L.MarkerClusterGroup(); 35 | var marker = new L.Marker([1.5, 1.5]); 36 | 37 | group.addLayer(marker); 38 | map.addLayer(group); 39 | 40 | var vp = group.getVisibleParent(marker); 41 | 42 | expect(vp).to.be(marker); 43 | }); 44 | 45 | it('gets the visible cluster if it is clustered', function () { 46 | group = new L.MarkerClusterGroup(); 47 | var marker = new L.Marker([1.5, 1.5]); 48 | var marker2 = new L.Marker([1.5, 1.5]); 49 | 50 | group.addLayers([marker, marker2]); 51 | map.addLayer(group); 52 | 53 | var vp = group.getVisibleParent(marker); 54 | 55 | expect(vp).to.be.a(L.MarkerCluster); 56 | expect(vp._icon).to.not.be(null); 57 | expect(vp._icon).to.not.be(undefined); 58 | }); 59 | 60 | it('returns null if the marker and parents are all not visible', function () { 61 | group = new L.MarkerClusterGroup(); 62 | var marker = new L.Marker([5.5, 1.5]); 63 | var marker2 = new L.Marker([5.5, 1.5]); 64 | 65 | group.addLayers([marker, marker2]); 66 | map.addLayer(group); 67 | 68 | var vp = group.getVisibleParent(marker); 69 | 70 | expect(vp).to.be(null); 71 | }); 72 | }); -------------------------------------------------------------------------------- /spec/suites/markerMoveSupportSpec.js: -------------------------------------------------------------------------------- 1 | describe('moving markers', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group, clock; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { maxZoom: 18, trackResize: false }); 16 | 17 | // Corresponds to zoom level 8 for the above div dimensions. 18 | map.fitBounds(new L.LatLngBounds([ 19 | [1, 1], 20 | [2, 2] 21 | ])); 22 | }); 23 | 24 | afterEach(function () { 25 | if (group instanceof L.MarkerClusterGroup) { 26 | group.clearLayers(); 27 | map.removeLayer(group); 28 | } 29 | 30 | map.remove(); 31 | div.remove(); 32 | clock.restore(); 33 | 34 | div = map = group = clock; 35 | }); 36 | 37 | 38 | ///////////////////////////// 39 | // TESTS 40 | ///////////////////////////// 41 | it('moves a marker that was moved while off the map', function () { 42 | 43 | group = new L.MarkerClusterGroup(); 44 | 45 | var marker = new L.Marker([10, 10]); 46 | map.addLayer(group); 47 | group.addLayer(marker); 48 | 49 | map.removeLayer(group); 50 | marker.setLatLng([1.5, 1.5]); 51 | map.addLayer(group); 52 | 53 | expect(group.getLayers().length).to.be(1); 54 | }); 55 | 56 | it('moves multiple markers that were moved while off the map', function () { 57 | 58 | group = new L.MarkerClusterGroup(); 59 | map.addLayer(group); 60 | 61 | var markers = []; 62 | for (var i = 0; i < 10; i++) { 63 | var marker = new L.Marker([10, 10]); 64 | group.addLayer(marker); 65 | markers.push(marker); 66 | } 67 | 68 | map.removeLayer(group); 69 | for (var i = 0; i < 10; i++) { 70 | var marker = markers[i]; 71 | marker.setLatLng([1.5, 1.5]); 72 | } 73 | map.addLayer(group); 74 | 75 | expect(group.getLayers().length).to.be(10); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /spec/suites/nonIntegerZoomSpec.js: -------------------------------------------------------------------------------- 1 | describe('non-integer min/max zoom', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div, clock; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { minZoom: 0.5, maxZoom: 18.5, trackResize: false }); 16 | 17 | map.fitBounds(new L.LatLngBounds([ 18 | [1, 1], 19 | [2, 2] 20 | ])); 21 | }); 22 | 23 | afterEach(function () { 24 | map.remove(); 25 | document.body.removeChild(div); 26 | clock.restore(); 27 | }); 28 | 29 | ///////////////////////////// 30 | // TESTS 31 | ///////////////////////////// 32 | it('dont break adding and removing markers', function () { 33 | var group = new L.MarkerClusterGroup(); 34 | var marker = new L.Marker([1.5, 1.5]); 35 | var marker2 = new L.Marker([1.5, 1.5]); 36 | var marker3 = new L.Marker([1.5, 1.5]); 37 | 38 | group.addLayer(marker); 39 | group.addLayer(marker2); 40 | map.addLayer(group); 41 | 42 | group.addLayer(marker3); 43 | 44 | expect(marker._icon).to.be(undefined); 45 | expect(marker2._icon).to.be(undefined); 46 | expect(marker3._icon).to.be(undefined); 47 | 48 | expect(map._panes.markerPane.childNodes.length).to.be(1); 49 | 50 | group.removeLayer(marker2); 51 | }); 52 | }); -------------------------------------------------------------------------------- /spec/suites/onAddSpec.js: -------------------------------------------------------------------------------- 1 | describe('onAdd', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { trackResize: false }); 14 | 15 | map.fitBounds(new L.LatLngBounds([ 16 | [1, 1], 17 | [2, 2] 18 | ])); 19 | }); 20 | 21 | afterEach(function () { 22 | map.remove(); 23 | document.body.removeChild(div); 24 | 25 | map = div = null; 26 | }); 27 | 28 | ///////////////////////////// 29 | // TESTS 30 | ///////////////////////////// 31 | it('throws an error if maxZoom is not specified', function () { 32 | 33 | var group = new L.MarkerClusterGroup(); 34 | var marker = new L.Marker([1.5, 1.5]); 35 | 36 | group.addLayer(marker); 37 | 38 | var ex = null; 39 | try { 40 | map.addLayer(group); 41 | } catch (e) { 42 | ex = e; 43 | } 44 | 45 | expect(ex).to.not.be(null); 46 | }); 47 | 48 | it('successfully handles removing and re-adding a layer while not on the map', function () { 49 | map.options.maxZoom = 18; 50 | var group = new L.MarkerClusterGroup(); 51 | var marker = new L.Marker([1.5, 1.5]); 52 | 53 | map.addLayer(group); 54 | group.addLayer(marker); 55 | 56 | map.removeLayer(group); 57 | group.removeLayer(marker); 58 | group.addLayer(marker); 59 | 60 | map.addLayer(group); 61 | 62 | expect(map.hasLayer(group)).to.be(true); 63 | expect(group.hasLayer(marker)).to.be(true); 64 | }); 65 | }); -------------------------------------------------------------------------------- /spec/suites/onRemoveSpec.js: -------------------------------------------------------------------------------- 1 | describe('onRemove', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var map, div; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | map.fitBounds(new L.LatLngBounds([ 16 | [1, 1], 17 | [2, 2] 18 | ])); 19 | }); 20 | 21 | afterEach(function () { 22 | map.remove(); 23 | document.body.removeChild(div); 24 | 25 | map = div = null; 26 | }); 27 | 28 | ///////////////////////////// 29 | // TESTS 30 | ///////////////////////////// 31 | it('removes the shown coverage polygon', function () { 32 | 33 | var group = new L.MarkerClusterGroup(); 34 | var marker = new L.Marker([1.5, 1.5]); 35 | var marker2 = new L.Marker([1.5, 1.5]); 36 | var marker3 = new L.Marker([1.5, 1.5]); 37 | 38 | group.addLayer(marker); 39 | group.addLayer(marker2); 40 | group.addLayer(marker3); 41 | 42 | map.addLayer(group); 43 | 44 | group._showCoverage({ layer: group._topClusterLevel }); 45 | 46 | expect(group._shownPolygon).to.not.be(null); 47 | 48 | map.removeLayer(group); 49 | 50 | expect(group._shownPolygon).to.be(null); 51 | }); 52 | }); -------------------------------------------------------------------------------- /spec/suites/removeLayersSpec.js: -------------------------------------------------------------------------------- 1 | describe('removeLayers', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group, clock; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { maxZoom: 18, trackResize: false }); 16 | 17 | // Corresponds to zoom level 8 for the above div dimensions. 18 | map.fitBounds(new L.LatLngBounds([ 19 | [1, 1], 20 | [2, 2] 21 | ])); 22 | }); 23 | 24 | afterEach(function () { 25 | if (group instanceof L.MarkerClusterGroup) { 26 | group.clearLayers(); 27 | map.removeLayer(group); 28 | } 29 | 30 | map.remove(); 31 | div.remove(); 32 | clock.restore(); 33 | 34 | div = map = group = clock = null; 35 | }); 36 | 37 | ///////////////////////////// 38 | // TESTS 39 | ///////////////////////////// 40 | it('removes all the layer given to it', function () { 41 | 42 | group = new L.MarkerClusterGroup(); 43 | 44 | var markers = [ 45 | new L.Marker([1.5, 1.5]), 46 | new L.Marker([1.5, 1.5]), 47 | new L.Marker([1.5, 1.5]) 48 | ]; 49 | 50 | map.addLayer(group); 51 | 52 | group.addLayers(markers); 53 | 54 | group.removeLayers(markers); 55 | 56 | expect(group.hasLayer(markers[0])).to.be(false); 57 | expect(group.hasLayer(markers[1])).to.be(false); 58 | expect(group.hasLayer(markers[2])).to.be(false); 59 | 60 | expect(group.getLayers().length).to.be(0); 61 | }); 62 | 63 | it('removes all the layer given to it even though they move', function () { 64 | 65 | group = new L.MarkerClusterGroup(); 66 | 67 | var markers = [ 68 | new L.Marker([10, 10]), 69 | new L.Marker([20, 20]), 70 | new L.Marker([30, 30]) 71 | ]; 72 | var len = markers.length; 73 | map.addLayer(group); 74 | 75 | group.addLayers(markers); 76 | 77 | markers.forEach(function (marker) { 78 | marker.setLatLng([1.5, 1.5]); 79 | group.removeLayer(marker); 80 | expect(group.getLayers().length).to.be(len - 1); 81 | group.addLayer(marker); 82 | expect(group.getLayers().length).to.be(len); 83 | }); 84 | 85 | expect(group.getLayers().length).to.be(len); 86 | }); 87 | 88 | it('removes all the layer given to it even if the group is not on the map', function () { 89 | 90 | group = new L.MarkerClusterGroup(); 91 | 92 | var markers = [ 93 | new L.Marker([1.5, 1.5]), 94 | new L.Marker([1.5, 1.5]), 95 | new L.Marker([1.5, 1.5]) 96 | ]; 97 | 98 | map.addLayer(group); 99 | group.addLayers(markers); 100 | map.removeLayer(group); 101 | group.removeLayers(markers); 102 | map.addLayer(group); 103 | 104 | expect(group.hasLayer(markers[0])).to.be(false); 105 | expect(group.hasLayer(markers[1])).to.be(false); 106 | expect(group.hasLayer(markers[2])).to.be(false); 107 | 108 | expect(group.getLayers().length).to.be(0); 109 | }); 110 | 111 | it('doesnt break if we are spiderfied', function () { 112 | 113 | group = new L.MarkerClusterGroup(); 114 | 115 | var markers = [ 116 | new L.Marker([1.5, 1.5]), 117 | new L.Marker([1.5, 1.5]), 118 | new L.Marker([1.5, 1.5]) 119 | ]; 120 | 121 | map.addLayer(group); 122 | 123 | group.addLayers(markers); 124 | 125 | markers[0].__parent.spiderfy(); 126 | 127 | // We must wait for the spiderfy animation to timeout 128 | clock.tick(200); 129 | 130 | group.removeLayers(markers); 131 | 132 | expect(group.hasLayer(markers[0])).to.be(false); 133 | expect(group.hasLayer(markers[1])).to.be(false); 134 | expect(group.hasLayer(markers[2])).to.be(false); 135 | 136 | expect(group.getLayers().length).to.be(0); 137 | 138 | group.on('spiderfied', function() { 139 | expect(group._spiderfied).to.be(null); 140 | }); 141 | }); 142 | 143 | it('handles nested Layer Groups', function () { 144 | 145 | group = new L.MarkerClusterGroup(); 146 | 147 | var marker1 = new L.Marker([1.5, 1.5]); 148 | var marker2 = new L.Marker([1.5, 1.5]); 149 | var marker3 = new L.Marker([1.5, 1.5]); 150 | 151 | map.addLayer(group); 152 | 153 | group.addLayers([marker1, marker2, marker3]); 154 | 155 | expect(group.hasLayer(marker1)).to.be(true); 156 | expect(group.hasLayer(marker2)).to.be(true); 157 | expect(group.hasLayer(marker3)).to.be(true); 158 | 159 | group.removeLayers([ 160 | marker1, 161 | new L.LayerGroup([ 162 | marker2, new L.LayerGroup([ 163 | marker3 164 | ]) 165 | ]) 166 | ]); 167 | 168 | expect(group.hasLayer(marker1)).to.be(false); 169 | expect(group.hasLayer(marker2)).to.be(false); 170 | expect(group.hasLayer(marker3)).to.be(false); 171 | 172 | expect(group.getLayers().length).to.be(0); 173 | }); 174 | 175 | it('chunked loading zoom out', function () { 176 | //See #743 for more details 177 | var markers = []; 178 | 179 | group = new L.MarkerClusterGroup({ 180 | chunkedLoading: true, chunkProgress: function () { 181 | //Before this provoked an "undefined" exception 182 | map.zoomOut(); 183 | group.removeLayers(markers); 184 | } 185 | }); 186 | 187 | for (var i = 1; i < 1000; i++) { 188 | markers.push(new L.Marker([1.0 + (.0001 * i), 1.0 + (.0001 * i)])); 189 | } 190 | 191 | map.addLayer(group); 192 | 193 | group.addLayers(markers); 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /spec/suites/removeOutsideVisibleBoundsSpec.js: -------------------------------------------------------------------------------- 1 | describe('Option removeOutsideVisibleBounds', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var marker1, marker2, marker3, marker4, marker5, markers, div, map, group, clock, realBrowser; 6 | 7 | beforeEach(function () { 8 | realBrowser = L.Browser; 9 | clock = sinon.useFakeTimers(); 10 | 11 | marker1 = L.marker([1.5, -0.4]); // 2 screens width away. 12 | marker2 = L.marker([1.5, 0.6]); // 1 screen width away. 13 | marker3 = L.marker([1.5, 1.5]); // In view port. 14 | marker4 = L.marker([1.5, 2.4]); // 1 screen width away. 15 | marker5 = L.marker([1.5, 3.4]); // 2 screens width away. 16 | markers = [marker1, marker2, marker3, marker4, marker5]; 17 | 18 | div = document.createElement('div'); 19 | div.style.width = '200px'; 20 | div.style.height = '200px'; 21 | document.body.appendChild(div); 22 | 23 | map = L.map(div, { maxZoom: 18, trackResize: false }); 24 | 25 | // Corresponds to zoom level 8 for the above div dimensions. 26 | map.fitBounds(new L.LatLngBounds([ 27 | [1, 1], 28 | [2, 2] 29 | ])); 30 | 31 | // Add all markers once to map then remove them immediately so that their icon is null (instead of undefined). 32 | for (i = 0; i < markers.length; i++) { 33 | map.removeLayer(markers[i].addTo(map)); 34 | } 35 | }); 36 | 37 | afterEach(function () { 38 | if (group instanceof L.MarkerClusterGroup) { 39 | //group.removeLayers(group.getLayers()); 40 | group.clearLayers(); 41 | map.removeLayer(group); 42 | } 43 | 44 | map.remove(); 45 | div.remove(); 46 | clock.restore(); 47 | 48 | marker1 = marker2 = marker3 = marker4 = marker5 = markers = div = map = group = clock = null; 49 | }); 50 | 51 | function prepareGroup() { 52 | // "group" should be assigned with a Marker Cluster Group before calling this function. 53 | group.addTo(map); 54 | 55 | group.addLayers(markers); 56 | } 57 | 58 | function setBrowserToMobile() { 59 | var fakeBrowser = {}; 60 | for (k in realBrowser) { 61 | fakeBrowser[k] = realBrowser[k]; 62 | } 63 | fakeBrowser.mobile = true; 64 | L.Browser = fakeBrowser; 65 | } 66 | 67 | ///////////////////////////// 68 | // TESTS 69 | ///////////////////////////// 70 | it('removes objects more than 1 screen away from view port by default', function () { 71 | 72 | group = L.markerClusterGroup(); 73 | 74 | prepareGroup(); 75 | 76 | expect(marker1._icon).to.be(null); 77 | expect(map._panes.markerPane.childNodes.length).to.be(3); // markers 2, 3 and 4. 78 | expect(marker5._icon).to.be(null); 79 | 80 | }); 81 | 82 | it('removes objects out of view port by default for mobile device', function () { 83 | setBrowserToMobile(); 84 | try { 85 | group = L.markerClusterGroup(); 86 | 87 | prepareGroup(); 88 | 89 | expect(marker1._icon).to.be(null); 90 | expect(marker2._icon).to.be(null); 91 | expect(map._panes.markerPane.childNodes.length).to.be(1); // marker 3 only. 92 | expect(marker4._icon).to.be(null); 93 | expect(marker5._icon).to.be(null); 94 | } 95 | finally { 96 | L.Browser = realBrowser; 97 | } 98 | }); 99 | 100 | it('leaves all objects on map when set to false', function () { 101 | 102 | group = L.markerClusterGroup({ 103 | removeOutsideVisibleBounds: false 104 | }); 105 | 106 | prepareGroup(); 107 | 108 | expect(map._panes.markerPane.childNodes.length).to.be(5); // All 5 markers. 109 | 110 | }); 111 | 112 | 113 | // Following tests need markers at very high latitude. 114 | // They test the _checkBoundsMaxLat method against the default Web/Spherical Mercator projection maximum latitude (85.0511287798). 115 | // The actual map view should be '-1.0986328125,84.92929204957956,1.0986328125,85.11983467698401' 116 | // The expanded bounds without correction should be '-3.2958984375,84.7387494221751,3.2958984375,85.31037730438847' 117 | var latLngsMaxLatDefault = [ 118 | [100, 3], // Impossible in real world, but nothing prevents the user from entering such latitude, and Web/Spherical Mercator projection will still display it at 85.0511287798 119 | [85.2, 1.5], // 1 "screen" heights away. 120 | [85, 0], // In center of view. 121 | [84.8, -1.5], // 1 "screen" height away. 122 | [84.6, -3] // 2 "screens" height away. 123 | ]; 124 | 125 | function moveMarkersAndMapToMaxLat(latLngs, isSouth) { 126 | for (i = 0; i < markers.length; i++) { 127 | if (isSouth) { 128 | markers[i].setLatLng([-latLngs[i][0], latLngs[i][1]]); 129 | } else { 130 | markers[i].setLatLng(latLngs[i]); 131 | } 132 | } 133 | 134 | map.fitBounds([ 135 | [isSouth ? -86 : 85, -1], 136 | [isSouth ? -85 : 86, 1] // The actual map view longitude span will be wider. '-1.0986328125,84.92929204957956,1.0986328125,85.11983467698401' 137 | ]); 138 | } 139 | 140 | function checkProjection(latLngs) { 141 | expect(map.options.crs).to.equal(L.CRS.EPSG3857); 142 | expect(L.CRS.EPSG3857.projection).to.equal(L.Projection.SphericalMercator); 143 | expect(L.Projection.SphericalMercator.MAX_LATITUDE).to.be.a('number'); 144 | 145 | var mapZoom = map.getZoom(); 146 | 147 | for (i = 0; i < markers.length; i++) { 148 | try { 149 | expect(markers[i].__parent._zoom).to.be.below(mapZoom); 150 | } catch (e) { 151 | console.log("Failed marker: " + (i + 1)); 152 | throw e; 153 | } 154 | } 155 | } 156 | 157 | it('includes objects above the Web Mercator projection maximum limit by default', function () { 158 | 159 | moveMarkersAndMapToMaxLat(latLngsMaxLatDefault); 160 | 161 | group = L.markerClusterGroup(); 162 | 163 | prepareGroup(); 164 | 165 | checkProjection(latLngsMaxLatDefault); 166 | 167 | expect(map._panes.markerPane.childNodes.length).to.be(4); // Markers 1, 2, 3 and 4. 168 | expect(marker5._icon).to.be(null); 169 | 170 | }); 171 | 172 | it('includes objects below the Web Mercator projection minimum limit by default', function () { 173 | 174 | moveMarkersAndMapToMaxLat(latLngsMaxLatDefault, true); 175 | 176 | // Make sure we are really in Southern hemisphere. 177 | expect(map.getBounds().getNorth()).to.be.below(-80); 178 | 179 | group = L.markerClusterGroup(); 180 | 181 | prepareGroup(); 182 | 183 | checkProjection(latLngsMaxLatDefault); 184 | 185 | clock.tick(1000); 186 | 187 | expect(map._panes.markerPane.childNodes.length).to.be(4); // Markers 1, 2, 3 and 4. 188 | expect(marker5._icon).to.be(null); 189 | 190 | }); 191 | 192 | 193 | // The actual map view should be '-1.0986328125,84.92929204957956,1.0986328125,85.11983467698401' 194 | var latLngsMaxLatMobile = [ 195 | [100, 1], // Impossible in real world, but nothing prevents the user from entering such latitude, and Web/Spherical Mercator projection will still display it at 85.0511287798 196 | [85.2, 0.5], // 1 "screen" heights away, but should be included by the correction. 197 | [85, 0], // In center of view. 198 | [84.9, -1], // 1 "screen" height away. 199 | [84.8, -1.5] // 2 "screens" height away. 200 | ]; 201 | 202 | it('includes objects above the Web Mercator projection maximum limit for mobile device', function () { 203 | setBrowserToMobile(); 204 | try { 205 | moveMarkersAndMapToMaxLat(latLngsMaxLatMobile); 206 | 207 | group = L.markerClusterGroup({ 208 | maxClusterRadius: 10 209 | }); 210 | 211 | prepareGroup(); 212 | 213 | checkProjection(latLngsMaxLatMobile); 214 | 215 | expect(map._panes.markerPane.childNodes.length).to.be(3); // Markers 1, 2 and 3. 216 | expect(marker4._icon).to.be(null); 217 | expect(marker5._icon).to.be(null); 218 | } 219 | finally { 220 | L.Browser = realBrowser; 221 | } 222 | }); 223 | 224 | it('includes objects below the Web Mercator projection minimum limit for mobile device', function () { 225 | setBrowserToMobile(); 226 | try { 227 | moveMarkersAndMapToMaxLat(latLngsMaxLatMobile, true); 228 | 229 | // Make sure we are really in Southern hemisphere. 230 | expect(map.getBounds().getNorth()).to.be.below(-80); 231 | 232 | group = L.markerClusterGroup({ 233 | maxClusterRadius: 10 234 | }); 235 | 236 | prepareGroup(); 237 | 238 | checkProjection(latLngsMaxLatMobile); 239 | 240 | expect(map._panes.markerPane.childNodes.length).to.be(3); // Markers 1, 2 and 3. 241 | expect(marker4._icon).to.be(null); 242 | expect(marker5._icon).to.be(null); 243 | } 244 | finally { 245 | L.Browser = realBrowser; 246 | } 247 | }); 248 | }); 249 | -------------------------------------------------------------------------------- /spec/suites/singleMarkerModeSpec.js: -------------------------------------------------------------------------------- 1 | describe('singleMarkerMode option', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group, defaultIcon, clusterIcon, marker; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { maxZoom: 18, trackResize: false }); 14 | 15 | // Corresponds to zoom level 8 for the above div dimensions. 16 | map.fitBounds(new L.LatLngBounds([ 17 | [1, 1], 18 | [2, 2] 19 | ])); 20 | 21 | defaultIcon = new L.Icon.Default(); 22 | clusterIcon = new L.Icon.Default(); 23 | marker = L.marker([1.5, 1.5]); 24 | marker.setIcon(defaultIcon); 25 | }); 26 | 27 | afterEach(function () { 28 | if (group instanceof L.MarkerClusterGroup) { 29 | group.removeLayers(group.getLayers()); 30 | map.removeLayer(group); 31 | } 32 | 33 | map.remove(); 34 | div.remove(); 35 | 36 | div = map = group = defaultIcon = clusterIcon = marker = null 37 | }); 38 | 39 | ///////////////////////////// 40 | // TESTS 41 | ///////////////////////////// 42 | it('overrides marker icons when set to true', function () { 43 | 44 | group = L.markerClusterGroup({ 45 | singleMarkerMode: true, 46 | iconCreateFunction: function (layer) { 47 | return clusterIcon; 48 | } 49 | }).addTo(map); 50 | 51 | expect(marker.options.icon).to.equal(defaultIcon); 52 | 53 | marker.addTo(group); 54 | 55 | expect(marker.options.icon).to.equal(clusterIcon); 56 | 57 | }); 58 | 59 | it('does not modify marker icons by default (or set to false)', function () { 60 | 61 | group = L.markerClusterGroup({ 62 | iconCreateFunction: function (layer) { 63 | return clusterIcon; 64 | } 65 | }).addTo(map); 66 | 67 | expect(marker.options.icon).to.equal(defaultIcon); 68 | 69 | marker.addTo(group); 70 | 71 | expect(marker.options.icon).to.equal(defaultIcon); 72 | 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /spec/suites/supportNegativeZoomSpec.js: -------------------------------------------------------------------------------- 1 | describe('things behave correctly with negative minZoom', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group; 6 | 7 | beforeEach(function () { 8 | div = document.createElement('div'); 9 | div.style.width = '200px'; 10 | div.style.height = '200px'; 11 | document.body.appendChild(div); 12 | 13 | map = L.map(div, { minZoom: -3, maxZoom: 18, trackResize: false }); 14 | 15 | map.setView(L.latLng(0, 0), -3); 16 | }); 17 | 18 | afterEach(function () { 19 | if (group instanceof L.MarkerClusterGroup) { 20 | group.clearLayers(); 21 | map.removeLayer(group); 22 | } 23 | 24 | map.remove(); 25 | div.remove(); 26 | 27 | div = map = group = null; 28 | }); 29 | 30 | ///////////////////////////// 31 | // TESTS 32 | ///////////////////////////// 33 | it('shows a single marker added to the group before the group is added to the map', function () { 34 | 35 | group = new L.MarkerClusterGroup(); 36 | 37 | var marker = new L.Marker([1.5, 1.5]); 38 | 39 | group.addLayer(marker); 40 | map.addLayer(group); 41 | 42 | expect(marker._icon).to.not.be(undefined); 43 | expect(marker._icon.parentNode).to.be(map._panes.markerPane); 44 | }); 45 | 46 | it('shows a single marker added to the group after the group is added to the map', function () { 47 | 48 | group = new L.MarkerClusterGroup(); 49 | 50 | var marker = new L.Marker([1.5, 1.5]); 51 | 52 | map.addLayer(group); 53 | group.addLayer(marker); 54 | 55 | expect(marker._icon).to.not.be(undefined); 56 | expect(marker._icon.parentNode).to.be(map._panes.markerPane); 57 | }); 58 | 59 | it('creates a cluster when 2 overlapping markers are added before the group is added to the map', function () { 60 | 61 | group = new L.MarkerClusterGroup(); 62 | var marker = new L.Marker([1.5, 1.5]); 63 | var marker2 = new L.Marker([1.5, 1.5]); 64 | 65 | group.addLayer(marker); 66 | group.addLayer(marker2); 67 | map.addLayer(group); 68 | 69 | expect(marker._icon).to.be(undefined); 70 | expect(marker2._icon).to.be(undefined); 71 | 72 | expect(map._panes.markerPane.childNodes.length).to.be(1); 73 | }); 74 | it('creates a cluster when 2 overlapping markers are added after the group is added to the map', function () { 75 | 76 | group = new L.MarkerClusterGroup(); 77 | var marker = new L.Marker([1.5, 1.5]); 78 | var marker2 = new L.Marker([1.5, 1.5]); 79 | 80 | map.addLayer(group); 81 | group.addLayer(marker); 82 | group.addLayer(marker2); 83 | 84 | expect(marker._icon).to.be(null); //Null as was added and then removed 85 | expect(marker2._icon).to.be(undefined); 86 | 87 | expect(map._panes.markerPane.childNodes.length).to.be(1); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /spec/suites/unspiderfySpec.js: -------------------------------------------------------------------------------- 1 | describe('unspiderfy', function () { 2 | ///////////////////////////// 3 | // SETUP FOR EACH TEST 4 | ///////////////////////////// 5 | var div, map, group, clock; 6 | 7 | beforeEach(function () { 8 | clock = sinon.useFakeTimers(); 9 | 10 | div = document.createElement('div'); 11 | div.style.width = '200px'; 12 | div.style.height = '200px'; 13 | document.body.appendChild(div); 14 | 15 | map = L.map(div, { maxZoom: 18, trackResize: false }); 16 | 17 | // Corresponds to zoom level 8 for the above div dimensions. 18 | map.fitBounds(new L.LatLngBounds([ 19 | [1, 1], 20 | [2, 2] 21 | ])); 22 | }); 23 | 24 | afterEach(function () { 25 | if (group instanceof L.MarkerClusterGroup) { 26 | group.removeLayers(group.getLayers()); 27 | map.removeLayer(group); 28 | } 29 | map.remove(); 30 | div.remove(); 31 | 32 | clock.restore(); 33 | 34 | div = map = group = clock = null; 35 | }); 36 | 37 | ///////////////////////////// 38 | // TESTS 39 | ///////////////////////////// 40 | it('Unspiderfies 2 Markers', function () { 41 | 42 | group = new L.MarkerClusterGroup(); 43 | 44 | var marker = new L.Marker([1.5, 1.5]); 45 | var marker2 = new L.Marker([1.5, 1.5]); 46 | 47 | group.addLayer(marker); 48 | group.addLayer(marker2); 49 | map.addLayer(group); 50 | 51 | marker.__parent.spiderfy(); 52 | 53 | clock.tick(1000); 54 | 55 | group.unspiderfy(); 56 | 57 | clock.tick(1000); 58 | 59 | expect(map.hasLayer(marker)).to.be(false); 60 | expect(map.hasLayer(marker2)).to.be(false); 61 | }); 62 | 63 | it('Unspiderfies 2 CircleMarkers', function () { 64 | 65 | group = new L.MarkerClusterGroup(); 66 | 67 | var marker = new L.CircleMarker([1.5, 1.5]); 68 | var marker2 = new L.CircleMarker([1.5, 1.5]); 69 | 70 | group.addLayer(marker); 71 | group.addLayer(marker2); 72 | map.addLayer(group); 73 | 74 | marker.__parent.spiderfy(); 75 | 76 | clock.tick(1000); 77 | 78 | group.unspiderfy(); 79 | 80 | clock.tick(1000); 81 | 82 | expect(map.hasLayer(marker)).to.be(false); 83 | expect(map.hasLayer(marker2)).to.be(false); 84 | }); 85 | 86 | it('Unspiderfies 2 Circles', function () { 87 | 88 | group = new L.MarkerClusterGroup(); 89 | 90 | var marker = new L.Circle([1.5, 1.5], 10); 91 | var marker2 = new L.Circle([1.5, 1.5], 10); 92 | 93 | group.addLayer(marker); 94 | group.addLayer(marker2); 95 | map.addLayer(group); 96 | 97 | marker.__parent.spiderfy(); 98 | 99 | clock.tick(1000); 100 | 101 | group.unspiderfy(); 102 | 103 | clock.tick(1000); 104 | 105 | expect(map.hasLayer(marker)).to.be(false); 106 | expect(map.hasLayer(marker2)).to.be(false); 107 | }); 108 | 109 | it('fires unspiderfied event on unspiderfy', function (done) { 110 | 111 | group = new L.MarkerClusterGroup(); 112 | 113 | var marker = new L.Marker([1.5, 1.5]); 114 | var marker2 = new L.Marker([1.5, 1.5]); 115 | 116 | group.addLayers([marker, marker2]); 117 | map.addLayer(group); 118 | 119 | marker.__parent.spiderfy(); 120 | 121 | clock.tick(1000); 122 | 123 | // Add event listener 124 | group.on('unspiderfied', function (event) { 125 | expect(event.target).to.be(group); 126 | expect(event.cluster).to.be.a(L.Marker); 127 | expect(event.markers[1]).to.be(marker); 128 | expect(event.markers[0]).to.be(marker2); 129 | 130 | done(); 131 | }); 132 | 133 | group.unspiderfy(); 134 | 135 | clock.tick(1000); 136 | 137 | }); 138 | 139 | }); -------------------------------------------------------------------------------- /src/DistanceGrid.js: -------------------------------------------------------------------------------- 1 | 2 | L.DistanceGrid = function (cellSize) { 3 | this._cellSize = cellSize; 4 | this._sqCellSize = cellSize * cellSize; 5 | this._grid = {}; 6 | this._objectPoint = { }; 7 | }; 8 | 9 | L.DistanceGrid.prototype = { 10 | 11 | addObject: function (obj, point) { 12 | var x = this._getCoord(point.x), 13 | y = this._getCoord(point.y), 14 | grid = this._grid, 15 | row = grid[y] = grid[y] || {}, 16 | cell = row[x] = row[x] || [], 17 | stamp = L.Util.stamp(obj); 18 | 19 | this._objectPoint[stamp] = point; 20 | 21 | cell.push(obj); 22 | }, 23 | 24 | updateObject: function (obj, point) { 25 | this.removeObject(obj); 26 | this.addObject(obj, point); 27 | }, 28 | 29 | //Returns true if the object was found 30 | removeObject: function (obj, point) { 31 | var x = this._getCoord(point.x), 32 | y = this._getCoord(point.y), 33 | grid = this._grid, 34 | row = grid[y] = grid[y] || {}, 35 | cell = row[x] = row[x] || [], 36 | i, len; 37 | 38 | delete this._objectPoint[L.Util.stamp(obj)]; 39 | 40 | for (i = 0, len = cell.length; i < len; i++) { 41 | if (cell[i] === obj) { 42 | 43 | cell.splice(i, 1); 44 | 45 | if (len === 1) { 46 | delete row[x]; 47 | } 48 | 49 | return true; 50 | } 51 | } 52 | 53 | }, 54 | 55 | eachObject: function (fn, context) { 56 | var i, j, k, len, row, cell, removed, 57 | grid = this._grid; 58 | 59 | for (i in grid) { 60 | row = grid[i]; 61 | 62 | for (j in row) { 63 | cell = row[j]; 64 | 65 | for (k = 0, len = cell.length; k < len; k++) { 66 | removed = fn.call(context, cell[k]); 67 | if (removed) { 68 | k--; 69 | len--; 70 | } 71 | } 72 | } 73 | } 74 | }, 75 | 76 | getNearObject: function (point) { 77 | var x = this._getCoord(point.x), 78 | y = this._getCoord(point.y), 79 | i, j, k, row, cell, len, obj, dist, 80 | objectPoint = this._objectPoint, 81 | closestDistSq = this._sqCellSize, 82 | closest = null; 83 | 84 | for (i = y - 1; i <= y + 1; i++) { 85 | row = this._grid[i]; 86 | if (row) { 87 | 88 | for (j = x - 1; j <= x + 1; j++) { 89 | cell = row[j]; 90 | if (cell) { 91 | 92 | for (k = 0, len = cell.length; k < len; k++) { 93 | obj = cell[k]; 94 | dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point); 95 | if (dist < closestDistSq || 96 | dist <= closestDistSq && closest === null) { 97 | closestDistSq = dist; 98 | closest = obj; 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | return closest; 106 | }, 107 | 108 | _getCoord: function (x) { 109 | var coord = Math.floor(x / this._cellSize); 110 | return isFinite(coord) ? coord : x; 111 | }, 112 | 113 | _sqDist: function (p, p2) { 114 | var dx = p2.x - p.x, 115 | dy = p2.y - p.y; 116 | return dx * dx + dy * dy; 117 | } 118 | }; 119 | -------------------------------------------------------------------------------- /src/MarkerCluster.QuickHull.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012 the authors listed at the following URL, and/or 2 | the authors of referenced articles or incorporated external code: 3 | http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434 25 | */ 26 | 27 | (function () { 28 | L.QuickHull = { 29 | 30 | /* 31 | * @param {Object} cpt a point to be measured from the baseline 32 | * @param {Array} bl the baseline, as represented by a two-element 33 | * array of latlng objects. 34 | * @returns {Number} an approximate distance measure 35 | */ 36 | getDistant: function (cpt, bl) { 37 | var vY = bl[1].lat - bl[0].lat, 38 | vX = bl[0].lng - bl[1].lng; 39 | return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng)); 40 | }, 41 | 42 | /* 43 | * @param {Array} baseLine a two-element array of latlng objects 44 | * representing the baseline to project from 45 | * @param {Array} latLngs an array of latlng objects 46 | * @returns {Object} the maximum point and all new points to stay 47 | * in consideration for the hull. 48 | */ 49 | findMostDistantPointFromBaseLine: function (baseLine, latLngs) { 50 | var maxD = 0, 51 | maxPt = null, 52 | newPoints = [], 53 | i, pt, d; 54 | 55 | for (i = latLngs.length - 1; i >= 0; i--) { 56 | pt = latLngs[i]; 57 | d = this.getDistant(pt, baseLine); 58 | 59 | if (d > 0) { 60 | newPoints.push(pt); 61 | } else { 62 | continue; 63 | } 64 | 65 | if (d > maxD) { 66 | maxD = d; 67 | maxPt = pt; 68 | } 69 | } 70 | 71 | return { maxPoint: maxPt, newPoints: newPoints }; 72 | }, 73 | 74 | 75 | /* 76 | * Given a baseline, compute the convex hull of latLngs as an array 77 | * of latLngs. 78 | * 79 | * @param {Array} latLngs 80 | * @returns {Array} 81 | */ 82 | buildConvexHull: function (baseLine, latLngs) { 83 | var convexHullBaseLines = [], 84 | t = this.findMostDistantPointFromBaseLine(baseLine, latLngs); 85 | 86 | if (t.maxPoint) { // if there is still a point "outside" the base line 87 | convexHullBaseLines = 88 | convexHullBaseLines.concat( 89 | this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints) 90 | ); 91 | convexHullBaseLines = 92 | convexHullBaseLines.concat( 93 | this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints) 94 | ); 95 | return convexHullBaseLines; 96 | } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull 97 | return [baseLine[0]]; 98 | } 99 | }, 100 | 101 | /* 102 | * Given an array of latlngs, compute a convex hull as an array 103 | * of latlngs 104 | * 105 | * @param {Array} latLngs 106 | * @returns {Array} 107 | */ 108 | getConvexHull: function (latLngs) { 109 | // find first baseline 110 | var maxLat = false, minLat = false, 111 | maxLng = false, minLng = false, 112 | maxLatPt = null, minLatPt = null, 113 | maxLngPt = null, minLngPt = null, 114 | maxPt = null, minPt = null, 115 | i; 116 | 117 | for (i = latLngs.length - 1; i >= 0; i--) { 118 | var pt = latLngs[i]; 119 | if (maxLat === false || pt.lat > maxLat) { 120 | maxLatPt = pt; 121 | maxLat = pt.lat; 122 | } 123 | if (minLat === false || pt.lat < minLat) { 124 | minLatPt = pt; 125 | minLat = pt.lat; 126 | } 127 | if (maxLng === false || pt.lng > maxLng) { 128 | maxLngPt = pt; 129 | maxLng = pt.lng; 130 | } 131 | if (minLng === false || pt.lng < minLng) { 132 | minLngPt = pt; 133 | minLng = pt.lng; 134 | } 135 | } 136 | 137 | if (minLat !== maxLat) { 138 | minPt = minLatPt; 139 | maxPt = maxLatPt; 140 | } else { 141 | minPt = minLngPt; 142 | maxPt = maxLngPt; 143 | } 144 | 145 | var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs), 146 | this.buildConvexHull([maxPt, minPt], latLngs)); 147 | return ch; 148 | } 149 | }; 150 | }()); 151 | 152 | L.MarkerCluster.include({ 153 | getConvexHull: function () { 154 | var childMarkers = this.getAllChildMarkers(), 155 | points = [], 156 | p, i; 157 | 158 | for (i = childMarkers.length - 1; i >= 0; i--) { 159 | p = childMarkers[i].getLatLng(); 160 | points.push(p); 161 | } 162 | 163 | return L.QuickHull.getConvexHull(points); 164 | } 165 | }); 166 | -------------------------------------------------------------------------------- /src/MarkerClusterGroup.Refresh.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds 1 public method to MCG and 1 to L.Marker to facilitate changing 3 | * markers' icon options and refreshing their icon and their parent clusters 4 | * accordingly (case where their iconCreateFunction uses data of childMarkers 5 | * to make up the cluster icon). 6 | */ 7 | 8 | 9 | L.MarkerClusterGroup.include({ 10 | /** 11 | * Updates the icon of all clusters which are parents of the given marker(s). 12 | * In singleMarkerMode, also updates the given marker(s) icon. 13 | * @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)| 14 | * L.MarkerCluster|L.Marker (optional) list of markers (or single marker) whose parent 15 | * clusters need to be updated. If not provided, retrieves all child markers of this. 16 | * @returns {L.MarkerClusterGroup} 17 | */ 18 | refreshClusters: function (layers) { 19 | if (!layers) { 20 | layers = this._topClusterLevel.getAllChildMarkers(); 21 | } else if (layers instanceof L.MarkerClusterGroup) { 22 | layers = layers._topClusterLevel.getAllChildMarkers(); 23 | } else if (layers instanceof L.LayerGroup) { 24 | layers = layers._layers; 25 | } else if (layers instanceof L.MarkerCluster) { 26 | layers = layers.getAllChildMarkers(); 27 | } else if (layers instanceof L.Marker) { 28 | layers = [layers]; 29 | } // else: must be an Array(L.Marker)|Map(L.Marker) 30 | this._flagParentsIconsNeedUpdate(layers); 31 | this._refreshClustersIcons(); 32 | 33 | // In case of singleMarkerMode, also re-draw the markers. 34 | if (this.options.singleMarkerMode) { 35 | this._refreshSingleMarkerModeMarkers(layers); 36 | } 37 | 38 | return this; 39 | }, 40 | 41 | /** 42 | * Simply flags all parent clusters of the given markers as having a "dirty" icon. 43 | * @param layers Array(L.Marker)|Map(L.Marker) list of markers. 44 | * @private 45 | */ 46 | _flagParentsIconsNeedUpdate: function (layers) { 47 | var id, parent; 48 | 49 | // Assumes layers is an Array or an Object whose prototype is non-enumerable. 50 | for (id in layers) { 51 | // Flag parent clusters' icon as "dirty", all the way up. 52 | // Dumb process that flags multiple times upper parents, but still 53 | // much more efficient than trying to be smart and make short lists, 54 | // at least in the case of a hierarchy following a power law: 55 | // http://jsperf.com/flag-nodes-in-power-hierarchy/2 56 | parent = layers[id].__parent; 57 | while (parent) { 58 | parent._iconNeedsUpdate = true; 59 | parent = parent.__parent; 60 | } 61 | } 62 | }, 63 | 64 | /** 65 | * Re-draws the icon of the supplied markers. 66 | * To be used in singleMarkerMode only. 67 | * @param layers Array(L.Marker)|Map(L.Marker) list of markers. 68 | * @private 69 | */ 70 | _refreshSingleMarkerModeMarkers: function (layers) { 71 | var id, layer; 72 | 73 | for (id in layers) { 74 | layer = layers[id]; 75 | 76 | // Make sure we do not override markers that do not belong to THIS group. 77 | if (this.hasLayer(layer)) { 78 | // Need to re-create the icon first, then re-draw the marker. 79 | layer.setIcon(this._overrideMarkerIcon(layer)); 80 | } 81 | } 82 | } 83 | }); 84 | 85 | L.Marker.include({ 86 | /** 87 | * Updates the given options in the marker's icon and refreshes the marker. 88 | * @param options map object of icon options. 89 | * @param directlyRefreshClusters boolean (optional) true to trigger 90 | * MCG.refreshClustersOf() right away with this single marker. 91 | * @returns {L.Marker} 92 | */ 93 | refreshIconOptions: function (options, directlyRefreshClusters) { 94 | var icon = this.options.icon; 95 | 96 | L.setOptions(icon, options); 97 | 98 | this.setIcon(icon); 99 | 100 | // Shortcut to refresh the associated MCG clusters right away. 101 | // To be used when refreshing a single marker. 102 | // Otherwise, better use MCG.refreshClusters() once at the end with 103 | // the list of modified markers. 104 | if (directlyRefreshClusters && this.__parent) { 105 | this.__parent._group.refreshClusters(this); 106 | } 107 | 108 | return this; 109 | } 110 | }); 111 | -------------------------------------------------------------------------------- /src/MarkerOpacity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Extends L.Marker to include two extra methods: clusterHide and clusterShow. 3 | * 4 | * They work as setOpacity(0) and setOpacity(1) respectively, but 5 | * don't overwrite the options.opacity 6 | * 7 | */ 8 | 9 | L.Marker.include({ 10 | clusterHide: function () { 11 | var backup = this.options.opacity; 12 | this.setOpacity(0); 13 | this.options.opacity = backup; 14 | return this; 15 | }, 16 | 17 | clusterShow: function () { 18 | return this.setOpacity(this.options.opacity); 19 | } 20 | }); 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | export { MarkerClusterGroup } from './MarkerClusterGroup.js'; 3 | export { MarkerCluster } from './MarkerCluster.js'; 4 | import {} from './MarkerOpacity.js'; 5 | import {} from './DistanceGrid.js'; 6 | import {} from './MarkerCluster.QuickHull.js'; 7 | import {} from './MarkerCluster.Spiderfier.js'; 8 | import {} from './MarkerClusterGroup.Refresh.js'; 9 | --------------------------------------------------------------------------------