├── circle.yml ├── .npmignore ├── test ├── .eslintrc └── test.js ├── babel.config.js ├── glsl ├── sprite.frag └── sprite.vert ├── .eslintrc ├── .editorconfig ├── wgsl ├── sprite_frag.wgsl └── sprite_vert.wgsl ├── karma.config.js ├── .gitignore ├── LICENSE ├── package.json ├── demo ├── index.html ├── canvas.html ├── gl.html └── gpu.html ├── README.md ├── rollup.config.js ├── dist ├── maptalks.markercluster.min.js ├── maptalks.markercluster.es.js ├── maptalks.markercluster.js └── maptalks.markercluster.es.js.map └── index.js /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 6.9.0 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /**/* 2 | !dist/*.js 3 | !dist/*.map 4 | !dist/*.css 5 | !ACKNOWLEDGEMENT 6 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect" : true, 7 | "sinon": true, 8 | "happen": true 9 | }, 10 | "plugins": ["mocha"], 11 | "rules": { 12 | "no-var" : 0, 13 | "prefer-const": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'presets': [ 3 | ['@babel/env', { 4 | 'loose': true, 5 | 'modules': false 6 | }] 7 | ], 8 | 'plugins': [ 9 | ], 10 | 'ignore': [ 11 | 'dist/*.js' 12 | ], 13 | 'comments': false 14 | }; 15 | -------------------------------------------------------------------------------- /glsl/sprite.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D sourceTexture; 4 | uniform float layerOpacity; 5 | 6 | varying vec2 vTexCoord; 7 | varying float vOpacity; 8 | 9 | void main() { 10 | vec4 color = texture2D(sourceTexture, vTexCoord); 11 | gl_FragColor = color * vOpacity * layerOpacity; 12 | // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es6": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parser": "@babel/eslint-parser", 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | "globals": { 13 | }, 14 | "rules": { 15 | "no-var" : "error", 16 | "prefer-const": "error", 17 | "no-undef": 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /glsl/sprite.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aPosition; 2 | attribute vec2 aTexCoord; 3 | attribute float aOpacity; 4 | 5 | uniform vec2 resolution; 6 | uniform vec2 dxDy; 7 | 8 | varying vec2 vTexCoord; 9 | varying float vOpacity; 10 | 11 | void main() { 12 | vTexCoord = aTexCoord; 13 | vOpacity = aOpacity; 14 | vec2 position = (aPosition + dxDy) / resolution * 2.0 - 1.0; 15 | gl_Position = vec4(position, 0.0, 1.0); 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [{package,bower}.json] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [jsduck.json] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [.travis.yml] 23 | indent_style = space 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /wgsl/sprite_frag.wgsl: -------------------------------------------------------------------------------- 1 | struct FragmentInput { 2 | @location($i) vTexCoord: vec2f, 3 | @location($i) vOpacity: f32, 4 | }; 5 | 6 | struct MyAppUniforms { 7 | layerOpacity: f32, 8 | }; 9 | 10 | @group(0) @binding($b) var uniforms: MyAppUniforms; 11 | @group(0) @binding($b) var sourceTexture: texture_2d; 12 | @group(0) @binding($b) var sourceTextureSampler: sampler; 13 | 14 | @fragment 15 | fn main(fragmentInput: FragmentInput) -> @location(0) vec4f { 16 | let color = textureSample(sourceTexture, sourceTextureSampler, fragmentInput.vTexCoord); 17 | return color * fragmentInput.vOpacity * uniforms.layerOpacity; 18 | // return vec4f(1.0, 0.0, 0.0, 1.0); 19 | } 20 | -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json'); 2 | 3 | module.exports = { 4 | basePath : '.', 5 | frameworks: ['mocha', 'expect', 'expect-maptalks', 'happen'], 6 | files: [ 7 | 'node_modules/maptalks/dist/maptalks.js', 8 | 'dist/' + pkg.name + '.js', 9 | 'test/**/*.js' 10 | ], 11 | preprocessors: { 12 | }, 13 | browsers: ['Chrome'], 14 | reporters: ['mocha'], 15 | customLaunchers: { 16 | IE10: { 17 | base: 'IE', 18 | 'x-ua-compatible': 'IE=EmulateIE10' 19 | }, 20 | IE9: { 21 | base: 'IE', 22 | 'x-ua-compatible': 'IE=EmulateIE9' 23 | } 24 | }, 25 | singleRun : true 26 | }; 27 | -------------------------------------------------------------------------------- /wgsl/sprite_vert.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | @location($i) aPosition: vec2f, 3 | @location($i) aTexCoord: vec2f, 4 | @location($i) aOpacity: f32, 5 | }; 6 | 7 | struct VertexOutput { 8 | @builtin(position) Position: vec4f, 9 | @location($o) vTexCoord: vec2f, 10 | @location($o) vOpacity: f32, 11 | }; 12 | 13 | struct MyAppUniforms { 14 | resolution: vec2f, 15 | dxDy: vec2f, 16 | }; 17 | 18 | @group(0) @binding($b) var uniforms: MyAppUniforms; 19 | 20 | @vertex 21 | fn main(vertexInput: VertexInput) -> VertexOutput { 22 | var output: VertexOutput; 23 | 24 | output.vTexCoord = vertexInput.aTexCoord; 25 | output.vOpacity = vertexInput.aOpacity; 26 | 27 | let position = (vertexInput.aPosition + uniforms.dxDy) / uniforms.resolution * 2.0 - 1.0; 28 | output.Position = vec4f(position, 0.0, 1.0); 29 | 30 | return output; 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tmp/ 2 | .idea/ 3 | *.iml 4 | *.gz 5 | .tern-port 6 | doc/ 7 | 8 | ### *tags 9 | GPATH 10 | GRTAGS 11 | GTAGS 12 | TAGS 13 | 14 | ### OSX template 15 | .DS_Store 16 | 17 | # Created by .ignore support plugin (hsz.mobi) 18 | ### Node template 19 | # Logs 20 | logs 21 | *.log 22 | 23 | # Runtime data 24 | pids 25 | *.pid 26 | *.seed 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (http://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directory 44 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 45 | node_modules 46 | 47 | #example deploy config 48 | examples/replace.json 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 MapTalks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maptalks.markercluster", 3 | "version": "0.9.0", 4 | "description": "A layer of maptalks to cluster markers.", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/maptalks/maptalks.markercluster.git" 9 | }, 10 | "main": "dist/maptalks.markercluster.js", 11 | "module": "dist/maptalks.markercluster.es.js", 12 | "jsnext:main": "dist/maptalks.markercluster.es.js", 13 | "scripts": { 14 | "dev": "rollup -w -c rollup.config.js", 15 | "build": "rollup --environment BUILD:production -c rollup.config.js", 16 | "build-dev": "rollup -c rollup.config.js", 17 | "test": "karma start --single-run", 18 | "tdd": "karma start --no-single-run", 19 | "preversion": "npm run lint", 20 | "version": "npm run build", 21 | "lint": "eslint index.js test/**/*.js", 22 | "prepare": "npm run lint && npm run build" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.15.0", 26 | "@babel/eslint-parser": "^7.15.0", 27 | "@babel/preset-env": "^7.15.0", 28 | "@rollup/plugin-commonjs": "^20.0.0", 29 | "@rollup/plugin-json": "^4.1.0", 30 | "@rollup/plugin-node-resolve": "^13.0.4", 31 | "@rollup/plugin-replace": "4.0.0", 32 | "expect.js": "^0.3.1", 33 | "eslint": "^7.32.0", 34 | "eslint-plugin-mocha": "^9.0.0", 35 | "karma": "^6.4.0", 36 | "karma-chrome-launcher": "^3.1.1", 37 | "karma-expect": "^1.1.3", 38 | "karma-mocha": "^2.0.1", 39 | "karma-mocha-reporter": "^2.2.5", 40 | "minimist": "^1.2.0", 41 | "mocha": "^5.2.0", 42 | "rollup": "^2.56.2", 43 | "rollup-plugin-terser": "^7.0.2" 44 | }, 45 | "dependencies": { 46 | "maptalks": ">=1.0.0", 47 | "@maptalks/gl": ">=0.110.0", 48 | "@maptalks/vt": ">=0.109.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | maptalks.markercluster demo 5 | 6 | 7 | 8 | 9 | 10 | 21 | 22 | 23 |
24 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /demo/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | maptalks.markercluster demo 5 | 6 | 7 | 8 | 9 | 10 | 21 | 22 | 23 |
24 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /demo/gl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | maptalks.markercluster demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 25 | 26 | 27 | 29 |
30 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /demo/gpu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | maptalks.markercluster demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 25 | 26 | 27 | 29 |
30 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # maptalks.markercluster 2 | 3 | [![CircleCI](https://circleci.com/gh/maptalks/maptalks.markercluster.svg?style=shield)](https://circleci.com/gh/MapTalks/maptalks.markercluster) 4 | [![NPM Version](https://img.shields.io/npm/v/maptalks.markercluster.svg)](https://github.com/maptalks/maptalks.markercluster) 5 | 6 | A plugin of [maptalks.js](https://github.com/maptalks/maptalks.js) to draw markers as clusters. 7 | 8 | ![screenshot](https://cloud.githubusercontent.com/assets/13678919/25312742/3036acb0-2853-11e7-8e9b-baf58e318a9b.jpg) 9 | 10 | ## Examples 11 | 12 | * marker clusters of [50000 points](https://maptalks.github.io/maptalks.markercluster/demo/). (data from [Leaflet.Heat](https://github.com/Leaflet/Leaflet.heat)) 13 | 14 | ## Install 15 | 16 | * Install with npm: ```npm install maptalks.markercluster```. 17 | * Download from [dist directory](https://github.com/maptalks/maptalks.markercluster/tree/gh-pages/dist). 18 | * Use unpkg CDN: ```https://unpkg.com/maptalks.markercluster/dist/maptalks.markercluster.min.js``` 19 | 20 | ## Usage 21 | 22 | As a plugin, ```maptalks.markercluster``` must be loaded after ```maptalks.js``` in browsers. 23 | ```html 24 | 25 | 26 | 30 | ``` 31 | 32 | ## Supported Browsers 33 | 34 | IE 9-11, Chrome, Firefox, other modern and mobile browsers. 35 | 36 | ## API Reference 37 | 38 | ```ClusterLayer``` is a subclass of [maptalks.VectorLayer](https://maptalks.github.io/docs/api/VectorLayer.html) and inherits all the methods of its parent. 39 | 40 | ### `Constructor` 41 | 42 | ```javascript 43 | new maptalks.ClusterLayer(id, data, options) 44 | ``` 45 | 46 | * id **String** layer id 47 | * data **Marker[]** layer data, an array of maptalks.Marker 48 | * options **Object** options 49 | * maxClusterRadius **Number** max cluster radius (160 by default) 50 | * symbol **Object** symbol of clusters 51 | * textSymbol **Object** symbol of cluster texts 52 | * drawClusterText **Boolean** whether to draw cluster texts (true by default) 53 | * textSumProperty **String** property name to sum up to display as the cluster text 54 | * maxClusterZoom **Number** the max zoom to draw as clusters (null by default) 55 | * animation **Boolean** whether animate the clusters when zooming (true by default) 56 | * animationDuration **Number** the animation duration 57 | * noClusterWithOneMarker **Boolean** whether display cluster with only one marker (false by default) 58 | * Other options defined in [maptalks.VectorLayer](https://maptalks.org/maptalks.js/api/0.x/VectorLayer.html) 59 | 60 | ### `config(key, value)` 61 | 62 | config layer's options and redraw the layer if necessary 63 | 64 | ```javascript 65 | clusterLayer.config('maxClusterRadius', 100); 66 | clusterLayer.config({ 67 | 'textSymbol' : { 68 | 'textFaceName' : 'monospace', 69 | 'textSize' : 16 70 | } 71 | }); 72 | ``` 73 | 74 | **Returns** `this` 75 | 76 | ### `addMarker(marker)` 77 | 78 | add more markers 79 | 80 | * marker **Marker[]** markers to add 81 | 82 | **Returns** `this` 83 | 84 | ### `toJSON()` 85 | 86 | export the layer's JSON. 87 | 88 | ```javascript 89 | var json = clusterLayer.toJSON(); 90 | ``` 91 | 92 | **Returns** `Object` 93 | 94 | ## Contributing 95 | 96 | We welcome any kind of contributions including issue reportings, pull requests, documentation corrections, feature requests and any other helps. 97 | 98 | ## Develop 99 | 100 | The only source file is ```index.js```. 101 | 102 | It is written in ES6, transpiled by [babel](https://babeljs.io/) and tested with [mocha](https://mochajs.org) and [expect.js](https://github.com/Automattic/expect.js). 103 | 104 | ### Scripts 105 | 106 | * Install dependencies 107 | ```shell 108 | $ npm install 109 | ``` 110 | 111 | * Watch source changes and generate runnable bundle repeatedly 112 | ```shell 113 | $ gulp watch 114 | ``` 115 | 116 | * Tests 117 | ```shell 118 | $ npm test 119 | ``` 120 | 121 | * Watch source changes and run tests repeatedly 122 | ```shell 123 | $ gulp tdd 124 | ``` 125 | 126 | * Package and generate minified bundles to dist directory 127 | ```shell 128 | $ gulp minify 129 | ``` 130 | 131 | * Lint 132 | ```shell 133 | $ npm run lint 134 | ``` 135 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const { nodeResolve: resolve } = require('@rollup/plugin-node-resolve'); 2 | const commonjs = require('@rollup/plugin-commonjs'); 3 | const terser = require('rollup-plugin-terser').terser; 4 | const pkg = require('./package.json'); 5 | 6 | 7 | function glsl() { 8 | return { 9 | transform(code, id) { 10 | if (/\.vert$/.test(id) === false && /\.frag$/.test(id) === false && /\.glsl$/.test(id) === false) return null; 11 | let transformedCode = JSON.stringify(code.trim() 12 | .replace(/\r/g, '') 13 | .replace(/[ \t]*\/\/.*\n/g, '') // remove // 14 | .replace(/[ \t]*\/\*[\s\S]*?\*\//g, '') // remove /* */ 15 | .replace(/\n{2,}/g, '\n')); // # \n+ to \n;; 16 | transformedCode = `export default ${transformedCode};`; 17 | return { 18 | code: transformedCode, 19 | map: { mappings: '' } 20 | }; 21 | } 22 | }; 23 | } 24 | 25 | function wgsl() { 26 | return { 27 | transform(code, id) { 28 | if (/\.wgsl$/.test(id) === false) return null; 29 | let transformedCode = JSON.stringify(code.trim() 30 | // .replace(/(^\s*)|(\s*$)/gm, '') 31 | .replace(/\r/g, '') 32 | .replace(/[ \t]*\/\/.*\n/g, '') // remove // 33 | .replace(/[ \t]*\/\*[\s\S]*?\*\//g, '') // remove /* */ 34 | .replace(/\n{2,}/g, '\n')); // # \n+ to \n;; 35 | transformedCode = `export default ${transformedCode};`; 36 | return { 37 | code: transformedCode, 38 | map: { mappings: '' } 39 | }; 40 | } 41 | }; 42 | } 43 | 44 | 45 | const production = process.env.BUILD === 'production'; 46 | const outputFile = pkg.main; 47 | const outputESFile = pkg.module; 48 | const plugins = [ 49 | ].concat(production ? [ 50 | terser({ 51 | output : { 52 | keep_quoted_props: true, 53 | beautify: false, 54 | comments : '/^!/' 55 | } 56 | }) 57 | ] : []); 58 | 59 | const banner = `/*!\n * ${pkg.name} v${pkg.version}\n * LICENSE : ${pkg.license}\n * (c) 2016-${new Date().getFullYear()} maptalks.org\n */`; 60 | 61 | let outro = pkg.name + ' v' + pkg.version; 62 | if (pkg.peerDependencies && pkg.peerDependencies['maptalks']) { 63 | outro += `, requires maptalks@${pkg.peerDependencies.maptalks}.`; 64 | } 65 | 66 | outro = `typeof console !== 'undefined' && console.log('${outro}');`; 67 | 68 | module.exports = [ 69 | { 70 | input: 'index.js', 71 | plugins: [ 72 | glsl(), 73 | wgsl(), 74 | resolve({ 75 | browser: true, 76 | preferBuiltins: false 77 | }), 78 | commonjs({ 79 | // global keyword handling causes Webpack compatibility issues, so we disabled it: 80 | // https://github.com/mapbox/mapbox-gl-js/pull/6956 81 | ignoreGlobal: true 82 | }) 83 | ].concat(plugins), 84 | external: ['maptalks', '@maptalks/gl', '@maptalks/vt'], 85 | output: { 86 | globals: { 87 | 'maptalks': 'maptalks', 88 | '@maptalks/gl': 'maptalks', 89 | '@maptalks/vt': 'maptalks' 90 | }, 91 | banner, 92 | outro, 93 | extend: true, 94 | name: 'maptalks', 95 | file: outputFile, 96 | format: 'umd', 97 | sourcemap: true, 98 | }, 99 | watch: { 100 | include: ['index.js', '**/*/*.vert', '**/*/*.frag', '**/*/*.wgsl'] 101 | } 102 | }, 103 | { 104 | input: 'index.js', 105 | plugins: [ 106 | glsl(), 107 | wgsl(), 108 | resolve({ 109 | browser: true, 110 | preferBuiltins: false 111 | }), 112 | commonjs({ 113 | // global keyword handling causes Webpack compatibility issues, so we disabled it: 114 | // https://github.com/mapbox/mapbox-gl-js/pull/6956 115 | ignoreGlobal: true 116 | }) 117 | ].concat(plugins), 118 | external: ['maptalks', '@maptalks/gl', '@maptalks/vt'], 119 | output: { 120 | globals: { 121 | 'maptalks': 'maptalks', 122 | '@maptalks/gl': 'maptalks', 123 | '@maptalks/vt': 'maptalks' 124 | }, 125 | banner, 126 | outro, 127 | extend: true, 128 | name: 'maptalks', 129 | file: outputESFile, 130 | format: 'es', 131 | sourcemap: true, 132 | }, 133 | watch: { 134 | include: ['index.js'] 135 | } 136 | } 137 | ]; 138 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | describe('ClusterLayer', function () { 2 | var container, map; 3 | beforeEach(function () { 4 | container = document.createElement('div'); 5 | container.style.width = '400px'; 6 | container.style.height = '300px'; 7 | document.body.appendChild(container); 8 | map = new maptalks.Map(container, { 9 | center : [0, 0], 10 | zoom : 17 11 | }); 12 | }); 13 | 14 | afterEach(function () { 15 | map.remove(); 16 | maptalks.DomUtil.removeDomNode(container); 17 | }); 18 | 19 | it('should display marker when added with one marker', function (done) { 20 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter())]); 21 | layer.on('layerload', function () { 22 | expect(layer).to.be.painted(0, -1); 23 | done(); 24 | }) 25 | .addTo(map); 26 | }); 27 | 28 | it('should display cluster when added with 2 markers', function (done) { 29 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter()), new maptalks.Marker(map.getCenter())]); 30 | layer.on('layerload', function () { 31 | expect(layer).to.be.painted(); 32 | done(); 33 | }) 34 | .addTo(map); 35 | }); 36 | 37 | it('should display marker if remove a marker from 2 markers cluster', function (done) { 38 | var marker = new maptalks.Marker(map.getCenter()); 39 | var layer = new maptalks.ClusterLayer('g', [marker, new maptalks.Marker(map.getCenter())]); 40 | layer.once('layerload', function () { 41 | layer.once('layerload', function () { 42 | expect(layer).to.be.painted(0, -1); 43 | done(); 44 | }); 45 | marker.remove(); 46 | }) 47 | .addTo(map); 48 | }); 49 | 50 | it('should display if added again after removed', function (done) { 51 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter()), new maptalks.Marker(map.getCenter())]); 52 | layer.once('layerload', function () { 53 | expect(layer).to.be.painted(); 54 | map.removeLayer(layer); 55 | layer.once('layerload', function () { 56 | expect(layer).to.be.painted(); 57 | done(); 58 | }); 59 | map.addLayer(layer); 60 | }); 61 | map.addLayer(layer); 62 | }); 63 | 64 | it('should show', function (done) { 65 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter()), new maptalks.Marker(map.getCenter())], { visible : false }); 66 | layer.once('add', function () { 67 | expect(layer).not.to.be.painted(); 68 | layer.once('layerload', function () { 69 | expect(layer).to.be.painted(); 70 | done(); 71 | }); 72 | layer.show(); 73 | }); 74 | map.addLayer(layer); 75 | }); 76 | 77 | it('should hide', function (done) { 78 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter()), new maptalks.Marker(map.getCenter())]); 79 | layer.once('layerload', function () { 80 | expect(layer).to.be.painted(); 81 | layer.once('hide', function () { 82 | expect(layer).not.to.be.painted(); 83 | done(); 84 | }); 85 | layer.hide(); 86 | }); 87 | map.addLayer(layer); 88 | }); 89 | 90 | it('should display markers when zoom is bigger than maxClusterZoom', function (done) { 91 | var symbol = { 92 | 'markerType' : 'ellipse', 93 | 'markerFill' : '#fff' 94 | }; 95 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter(), { symbol : symbol }), new maptalks.Marker(map.getCenter(), { symbol : symbol })], { 'maxClusterZoom' : 16 }); 96 | layer.on('layerload', function () { 97 | expect(layer).to.be.painted(0, -1, [255, 255, 255]); 98 | done(); 99 | }) 100 | .addTo(map); 101 | }); 102 | 103 | it('should be able to update marker\' symbol', function (done) { 104 | var marker = new maptalks.Marker(map.getCenter()); 105 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter()), marker], { 'maxClusterZoom' : 16 }); 106 | layer.once('layerload', function () { 107 | layer.once('layerload', function () { 108 | expect(layer).to.be.painted(0, 0, [255, 255, 255]); 109 | done(); 110 | }); 111 | marker.setSymbol({ 112 | 'markerType' : 'ellipse', 113 | 'markerFill' : '#fff' 114 | }); 115 | }) 116 | .addTo(map); 117 | }); 118 | 119 | it('should be able to update marker\' symbol by style', function (done) { 120 | var marker = new maptalks.Marker(map.getCenter()); 121 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter()), marker], { 'maxClusterZoom' : 16 }); 122 | layer.once('layerload', function () { 123 | layer.once('layerload', function () { 124 | expect(layer).to.be.painted(0, 0, [255, 255, 255]); 125 | done(); 126 | }); 127 | layer.setStyle([ 128 | { 129 | filter : true, 130 | symbol : { 131 | 'markerType' : 'ellipse', 132 | 'markerFill' : '#fff' 133 | } 134 | } 135 | ]); 136 | }) 137 | .addTo(map); 138 | }); 139 | 140 | it('should be able to identify', function (done) { 141 | var marker = new maptalks.Marker(map.getCenter()); 142 | var layer = new maptalks.ClusterLayer('g', [new maptalks.Marker(map.getCenter()), marker]); 143 | layer.once('layerload', function () { 144 | var hits = layer.identify(map.getCenter()); 145 | expect(hits).to.be.ok(); 146 | expect(hits.center).to.be.ok(); 147 | expect(hits.children.length === 2).to.be.ok(); 148 | done(); 149 | }) 150 | .addTo(map); 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /dist/maptalks.markercluster.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * maptalks.markercluster v0.8.8 3 | * LICENSE : MIT 4 | * (c) 2016-2024 maptalks.org 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("maptalks")):"function"==typeof define&&define.amd?define(["exports","maptalks"],e):e(t.maptalks=t.maptalks||{},t.maptalks)}(this,function(t,_){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function n(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function e(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):function(t,e){for(var r=Object.getOwnPropertyNames(e),o=0;oo?i.prototype.identify.call(this,t,e):this._getRenderer()?this._getRenderer().identify(t,e):null},s.prototype.toJSON=function(){var t=i.prototype.toJSON.call(this);return t.type="ClusterLayer",t},s.prototype.getClusters=function(){var t=this._getRenderer();return t&&t._currentClusters||[]},s}(_.VectorLayer);r.mergeOptions({maxClusterRadius:160,textSumProperty:null,symbol:null,drawClusterText:!0,textSymbol:null,animation:!0,animationDuration:450,maxClusterZoom:null,noClusterWithOneMarker:!0,forceRenderOnZooming:!0}),r.registerJSONType("ClusterLayer");var i={textFaceName:'"microsoft yahei"',textSize:16,textDx:0,textDy:0},s={markerType:"ellipse",markerFill:{property:"count",type:"interval",stops:[[0,"rgb(135, 196, 240)"],[9,"#1bbc9b"],[99,"rgb(216, 115, 149)"]]},markerFillOpacity:.7,markerLineOpacity:1,markerLineWidth:3,markerLineColor:"#fff",markerWidth:{property:"count",type:"interval",stops:[[0,40],[9,60],[99,80]]},markerHeight:{property:"count",type:"interval",stops:[[0,40],[9,60],[99,80]]}};r.registerRenderer("canvas",function(h){function r(t){o(this,r);var e=n(this,h.call(this,t));return e._animated=!0,e._refreshStyle(),e._clusterNeedRedraw=!0,e}return e(r,h),r.prototype.checkResources=function(){var t=this.layer.options.symbol||s,e=h.prototype.checkResources.apply(this,arguments);if(t!==this._symbolResourceChecked){var r=_.Util.getExternalResources(t,!0);r&&e.push.apply(e,r),this._symbolResourceChecked=t}return e},r.prototype.draw=function(){this.canvas||this.prepareCanvas();var t=this.getMap().getZoom(),e=this.layer.options.maxClusterZoom;if(e&&eo)return h.prototype.identify.call(this,t,e);if(this._currentClusters){for(var i=r.coordinateToContainerPoint(t),n=this._currentGrid,s=0;st._getResolution(t.getMaxZoom())?e-1:e+1;this._clusterCache[r]&&this._clusterCache[r].length===this.layer.getCount()&&(this._clusterCache[e]=this._clusterCache[r]),this._clusterCache[e]||(this._clusterCache[e]=this._computeZoomGrid(e))},r.prototype._computeZoomGrid=function(t){if(!this._markerExtent)return null;var e=this.getMap(),r=e._getResolution(t)*this.layer.options.maxClusterRadius,o=e._getResolution(t-1)?e._getResolution(t-1)*this.layer.options.maxClusterRadius:null,i=this._clusterCache[t-1];!i&&t-1>=e.getMinZoom()&&(this._clusterCache[t-1]=i=this._computeZoomGrid(t-1));for(var n=this._markerPoints,s=this.layer.options.textSumProperty,a={},l=this._markerExtent.getMin(),u=void 0,p=void 0,h=0,c=n.length;ht.to?"in":"out",this._animated=!0,this._computeGrid()),h.prototype.onZoomEnd.apply(this,arguments)},r.prototype._clearDataCache=function(){this._stopAnim(),delete this._markerExtent,delete this._markerPoints,delete this._clusterCache,delete this._zoomInClusters},r}(_.renderer.VectorLayerCanvasRenderer)),t.ClusterLayer=r,Object.defineProperty(t,"__esModule",{value:!0}),"undefined"!=typeof console&&console.log("maptalks.markercluster v0.8.8")}); -------------------------------------------------------------------------------- /dist/maptalks.markercluster.es.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * maptalks.markercluster v0.9.0 3 | * LICENSE : MIT 4 | * (c) 2016-2025 maptalks.org 5 | */ 6 | import*as t from"maptalks";import{reshader as e}from"@maptalks/gl";import{PointLayerRenderer as r,getVectorPacker as i}from"@maptalks/vt";const s=document.createElement("canvas").getContext("2d"),o=new t.Point(0,0),a=t.DrawToolLayer.markerLayerClazz;let n="canvas";a.getRendererClass("canvas")||(n="gl");const h={"renderer":n,"maxClusterRadius":160,"textSumProperty":null,"symbol":null,"drawClusterText":!0,"textSymbol":null,"animation":!0,"animationDuration":450,"maxClusterZoom":null,"noClusterWithOneMarker":!0,"forceRenderOnZooming":!0};class u extends a{static fromJSON(e){if(!e||"ClusterLayer"!==e.type)return null;const r=new u(e.id,e.options),i=e.geometries,s=[];for(let e=0;ei?super.identify(t,e):this._getRenderer()?this._getRenderer().identify(t,e):null}toJSON(){const t=super.toJSON.call(this);return t.type="ClusterLayer",t}getClusters(){const t=this._getRenderer();return t&&t._currentClusters||[]}}u.mergeOptions(h),u.registerJSONType("ClusterLayer");const d={"textFaceName":'"microsoft yahei"',"textSize":16,"textDx":0,"textDy":0},l={"markerType":"ellipse","markerFill":{property:"count",type:"interval",stops:[[0,"rgb(135, 196, 240)"],[9,"#1bbc9b"],[99,"rgb(216, 115, 149)"]]},"markerFillOpacity":.7,"markerLineOpacity":1,"markerLineWidth":3,"markerLineColor":"#fff","markerWidth":{property:"count",type:"interval",stops:[[0,40],[9,60],[99,80]]},"markerHeight":{property:"count",type:"interval",stops:[[0,40],[9,60],[99,80]]}},c=function(e){return class extends e{init(){this._refreshStyle(),this._clusterNeedRedraw=!0}checkResources(){if(!super.checkResources)return[];const e=this.layer.options.symbol||l,r=super.checkResources.apply(this,arguments);if(e!==this._symbolResourceChecked){const i=t.Util.getExternalResources(e,!0);i&&r.push.apply(r,i),this._symbolResourceChecked=e}return r}draw(){this.canvas||this.prepareCanvas();const t=this.getMap().getZoom(),e=this.layer.options.maxClusterZoom;if(e&&t>e)return delete this._currentClusters,this.checkMarksToDraw(),void super.draw.apply(this,arguments);let r;if(this._clusterNeedRedraw&&(this._clearDataCache(),this._computeGrid(),this._clusterNeedRedraw=!1),this._triggerAnimate&&this._startAnimation(t),this._animateDelta)r=this._animateClusters;else{const e=this._clusterCache[t]?this._clusterCache[t].clusters:null;r=this.getClustersToDraw(e),r.zoom=t}this._drawLayer(r)}_startAnimation(e){const r=this._clusterCache[e]?this._clusterCache[e].clusters:null,i=this.getClustersToDraw(r);i.zoom=e,this._animateClusters=i,this._parentClusters=this._currentClusters||i;const s=this.layer;if(s.options.animation&&this._triggerAnimate){let e=[0,1];"in"===this._inout&&(e=[1,0]),this._animateDelta=e[0],this._player=t.animation.Animation.animate({"d":e},{"speed":s.options.animationDuration,"easing":"inAndOut"},t=>{this._animateDelta=t.styles.d,"finished"===t.state.playState&&(delete this._animateDelta,delete this._inout,delete this._animateClusters,delete this._parentClusters),this.setToRedraw()}).play(),this.setToRedraw()}this._triggerAnimate=!1}checkMarksToDraw(){const t=this._markersToDraw!==this.layer._geoList;this._markersToDraw=this.layer._geoList,this._markersToDraw.dirty=t}getClustersToDraw(e){const r=this._markersToDraw||[];this._markersToDraw=[];const i=this.getMap(),s=t.StringUtil.getFont(this._textSymbol),o=t.StringUtil.stringLength("9",s).toPoint(),a=i.getContainerExtent(),n=[];let h,u,d,l,c,x=0,p=!1;for(const s in e){if(this._currentGrid=e[s],1===e[s].count&&this.layer.options.noClusterWithOneMarker){const t=e[s].children[0];t._cluster=e[s],p||r[x++]===t||(p=!0),this._markersToDraw.push(t);continue}if(d=this._getSprite().sprite,l=d.canvas.width,c=d.canvas.height,h=i._prjToContainerPoint(e[s].center),u=new t.PointExtent(h.sub(l,c),h.add(l,c)),a.intersects(u)){if(!e[s].textSize){const r=this._getClusterText(e[s]);e[s].textSize=new t.Point(o.x*r.length,o.y)._multi(.5)}n.push(e[s])}}return r.length!==this._markersToDraw.length&&(p=!0),this._markersToDraw.dirty=p,n}drawOnInteracting(...t){this._currentClusters&&this.drawClusters(this._currentClusters,1),super.drawOnInteracting(...t)}getCurrentNeedRenderGeos(){return this._markersToDraw?this._markersToDraw:[]}_getCurrentNeedRenderGeos(){return this.getCurrentNeedRenderGeos()}forEachGeo(t,e){this._markersToDraw&&this._markersToDraw.forEach(r=>{e?t.call(e,r):t(r)})}onGeometryShow(){this._clusterNeedRedraw=!0,super.onGeometryShow.apply(this,arguments)}onGeometryHide(){this._clusterNeedRedraw=!0,super.onGeometryHide.apply(this,arguments)}onGeometryAdd(){this._clusterNeedRedraw=!0,super.onGeometryAdd.apply(this,arguments)}onGeometryRemove(){this._clusterNeedRedraw=!0,super.onGeometryRemove.apply(this,arguments)}onGeometryPositionChange(){this._clusterNeedRedraw=!0,super.onGeometryPositionChange.apply(this,arguments)}onRemove(){this._clearDataCache()}identify(t,e){const r=this.getMap(),i=this.layer.options.maxClusterZoom;if(i&&r.getZoom()>i)return super.identify(t,e);if(this._currentClusters){const e=r.coordinateToContainerPoint(t),i=this._currentGrid;for(let t=0;t[this.getMap().getZoom(),this._currentGrid];this._symbol=t.MapboxUtil.loadFunctionTypes(e,i),this._textSymbol=t.MapboxUtil.loadFunctionTypes(r,i)}_drawLayer(t){this._currentClusters=t,this._animateDelta>=0?"in"===this._inout?this.drawClustersFrame(t,this._parentClusters,this._animateDelta):this.drawClustersFrame(this._parentClusters,t,this._animateDelta):this.drawClusters(t,1),this.drawMarkers(),this.completeRender()}drawMarkers(){super.drawGeos()}drawClustersFrame(t,e,r){this.prepareCanvas();const i=this.getMap(),s={};if(t&&t.forEach(t=>{const e=i._prjToContainerPoint(t.center);s[t.key]||(s[t.key]=1,this.drawCluster(e,t,1-r))}),0===r||!e)return;const o=t.zoom,a=i._getResolution(o)*this.layer.options.maxClusterRadius,n=this._markerExtent.getMin();e.forEach(t=>{let e=i._prjToContainerPoint(t.center);const s=t.center,h=Math.floor((s.x-n.x)/a)+"_"+Math.floor((s.y-n.y)/a),u=this._clusterCache[o]?this._clusterCache[o].clusterMap[h]:null;if(u){const t=i._prjToContainerPoint(u.center);e=t.add(e.sub(t)._multi(r))}this.drawCluster(e,t,r>.5?1:r)})}drawClusters(t,e){if(!t)return;this.prepareCanvas();const r=this.getMap();t.forEach(t=>{const i=r._prjToContainerPoint(t.center);this.drawCluster(i,t,e>.5?1:e)})}drawCluster(e,r,i){this._currentGrid=r;const s=this.context,o=this._getSprite().sprite,a=s.globalAlpha;if(a*i!==0){if(s.globalAlpha=a*i,o){const r=e.add(o.offset)._sub(o.canvas.width/2,o.canvas.height/2);t.Canvas.image(s,o.canvas,r.x,r.y)}if(this.layer.options.drawClusterText&&r.textSize){t.Canvas.prepareCanvasFont(s,this._textSymbol),s.textBaseline="middle";const i=this._textSymbol.textDx||0,o=this._textSymbol.textDy||0,a=this._getClusterText(r);t.Canvas.fillText(s,a,e.sub(r.textSize.x,0)._add(i,o))}s.globalAlpha=a}}_getClusterText(t){return(this.layer.options.textSumProperty?t.textSumProperty:t.count)+""}_getSprite(){this._spriteCache||(this._spriteCache={});const e=function(t){const e=[];for(const r in t)"_"!==r[0]&&e.push(t[r]);return e.join("|")}(this._symbol);return this._spriteCache[e]||(this._spriteCache[e]=new t.Marker([0,0],{"symbol":this._symbol})._getSprite(this.resources,this.getMap().CanvasClass)),{sprite:this._spriteCache[e],key:e}}_initGridSystem(){const t=[];let e,r;this.layer.forEach(i=>{i.isVisible()&&(r=i._getPrjCoordinates(),e=e?e._combine(i._getPrjExtent()):i._getPrjExtent(),t.push({x:r.x,y:r.y,id:i._getInternalId(),geometry:i}))}),this._markerExtent=e,this._markerPoints=t}_computeGrid(){const t=this.getMap(),e=t.getZoom();this._markerExtent||this._initGridSystem(),this._clusterCache||(this._clusterCache={});const r=t._getResolution(t.getMinZoom())>t._getResolution(t.getMaxZoom())?e-1:e+1;this._clusterCache[r]&&this._clusterCache[r].length===this.layer.getCount()&&(this._clusterCache[e]=this._clusterCache[r]),this._clusterCache[e]||(this._clusterCache[e]=this._computeZoomGrid(e))}_computeZoomGrid(e){if(!this._markerExtent)return null;const r=this.getMap(),i=r._getResolution(e)*this.layer.options.maxClusterRadius,s=r._getResolution(e-1)?r._getResolution(e-1)*this.layer.options.maxClusterRadius:null;let o=this._clusterCache[e-1];!o&&e-1>=r.getMinZoom()&&(this._clusterCache[e-1]=o=this._computeZoomGrid(e-1));const a=this._markerPoints,n=this.layer.options.textSumProperty,h={},u=this._markerExtent.getMin();let d,l,c,x,p,y;for(let e=0,r=a.length;et.to?"in":"out",this._triggerAnimate=!0,this._computeGrid(),super.onZoomEnd.apply(this,arguments)):super.onZoomEnd.apply(this,arguments)}_clearDataCache(){this._stopAnim(),delete this._markerExtent,delete this._markerPoints,delete this._clusterCache,delete this._zoomInClusters}}};class x extends(c(t.renderer.VectorLayerCanvasRenderer)){constructor(...t){super(...t),this.init()}}if(u.registerRenderer("canvas",x),void 0!==r){class a extends(c(r)){constructor(...t){super(...t),this.init()}drawOnInteracting(t,e,i){this._currentClusters&&this.drawClusters(this._currentClusters,1),this.drawMarkers(),r.prototype.draw.call(this,e,i)}drawClusters(...t){this._clearToDraw(),super.drawClusters(...t),this.flush()}drawClustersFrame(...t){this._clearToDraw(),super.drawClustersFrame(...t),this.flush()}_clearToDraw(){this.pointCount=0,this.bufferIndex=0,this.opacityIndex=0,this.textIndex=0}drawCluster(e,r,i){this._currentGrid=r;const{sprite:o,key:a}=this._getSprite(),n=o.canvas;o.data||(o.data=n.getContext("2d",{willReadFrequently:!0}).getImageData(0,0,n.width,n.height)),this.clusterSprites[a]||(this.clusterSprites[a]=o,this.textureDirty=!0);const h=e.add(o.offset)._sub(n.width/2,n.height/2);let u=h.x,d=h.y;const l=this.getMap(),c=l.getDevicePixelRatio();u*=c,d=(l.height-d)*c;const x=o.data.width*c,p=o.data.height*c;if(this.addPoint(u,d,x,p,i,a),this.layer.options.drawClusterText){t.Canvas.prepareCanvasFont(s,this._textSymbol);const e=s.font+"-"+s.fillStyle,i=this._getClusterText(r),{sprite:o,key:a}=this._getTextSprite(i,e);this.clusterTextSprites[a]||(this.clusterTextSprites[a]=o,this.textTextureDirty=!0),this.addTextPoint(u+x/2,d-p/2,o.data.width*c,o.data.height*c,a)}this.pointCount++}_getTextSprite(e,r){this._textSpriteCache||(this._textSpriteCache={});const i=r+"-"+e;if(!this._textSpriteCache[i]){const r=this.getMap().getDevicePixelRatio(),a=s.measureText(e),n=a.width,h=a.actualBoundingBoxAscent+a.actualBoundingBoxDescent,u=document.createElement("canvas");u.width=n*r,u.height=h*r;const d=u.getContext("2d",{willReadFrequently:!0});d.scale(r,r),t.Canvas.prepareCanvasFont(d,this._textSymbol),d.textBaseline="top",d.fillText(e,0,0);const l=document.getElementById("debug-text-sprite");if(l){l.width=u.width,l.height=u.height;l.getContext("2d").drawImage(u,0,0)}this._textSpriteCache[i]={canvas:u,offset:o,data:d.getImageData(0,0,u.width,u.height)}}return{sprite:this._textSpriteCache[i],key:i}}checkMarksToDraw(){super.checkMarksToDraw(),this.drawMarkers()}drawMarkers(){this._markersToDraw.dirty&&(this.rebuildGeometries(),this._markersToDraw.dirty=!1)}flush(t){if(0===this.pointCount)return;this._updateMesh();const e=t&&t.renderTarget&&context.renderTarget.fbo;this._clusterGeometry.setDrawCount(6*this.pointCount);const{width:r,height:i}=this.canvas,s={resolution:[r,i],layerOpacity:this._getLayerOpacity(),dxDy:[0,0]};if(this._renderer.render(this._spriteShader,s,this._scene,e),this.layer.options.drawClusterText){this._textGeometry.setDrawCount(6*this.pointCount);const t=this._textSymbol.textDx||0,r=this._textSymbol.textDy||0;s.dxDy=[t,r],this._renderer.render(this._spriteShader,s,this._textScene,e)}}_updateMesh(){const t=this.textureDirty,e=this._genAtlas();if(this._updateTexCoord(e,t),this.layer.options.drawClusterText){const t=this.textTextureDirty,e=this._genTextAtlas();this._updateTextTexCoord(e,t)}this._updateGeometryData()}addPoint(t,e,r,i,s,o){this._check();const a=r,n=i;this.addVertex(t,e-n,s),this.addVertex(t+a,e-n,s),this.addVertex(t,e,s),this.addVertex(t,e,s),this.addVertex(t+a,e-n,s),this.addVertex(t+a,e,s),this.sprites[this.pointCount]!==o&&(this.sprites[this.pointCount]=o,this.sprites.dirty=!0)}addVertex(t,e,r){const i=this.positionBufferData;i[this.bufferIndex]!==t&&(i[this.bufferIndex]=t,i.dirty=!0),this.bufferIndex++,i[this.bufferIndex]!==e&&(i[this.bufferIndex]=e,i.dirty=!0),this.bufferIndex++;const s=this.opacityBufferData;s[this.opacityIndex]!==r&&(s[this.opacityIndex]=r,s.dirty=!0),this.opacityIndex++}addTextPoint(t,e,r,i,s){this._check();const o=this.getMap().getDevicePixelRatio(),a=(r/=o)/2,n=(i/=o)/2;this.addTextVertex(t-a,e-n),this.addTextVertex(t+a,e-n),this.addTextVertex(t-a,e+n),this.addTextVertex(t-a,e+n),this.addTextVertex(t+a,e-n),this.addTextVertex(t+a,e+n),this.textSprites[this.pointCount]!==s&&(this.textSprites[this.pointCount]=s,this.textSprites.dirty=!0)}addTextVertex(t,e){const r=this.textPositionData;r[this.textIndex]!==t&&(r[this.textIndex]=t,r.dirty=!0),this.textIndex++,r[this.textIndex]!==e&&(r[this.textIndex]=e,r.dirty=!0),this.textIndex++}_check(){if(this.pointCount>=this.maxPointCount-1){this.maxPointCount+=1024;const{positionBufferData:t,texCoordBufferData:e,opacityBufferData:r,textPositionData:i,textTexCoordData:s}=this._initBuffers();for(let r=0;rthis.canvas?this.canvas.width:1,height:()=>this.canvas?this.canvas.height:1},depth:{enable:!1},blend:{enable:!0,func:{src:1,dst:"one minus src alpha"}}};this._spriteShader=new e.MeshShader({name:"cluster-sprite",vert:"attribute vec2 aPosition;\nattribute vec2 aTexCoord;\nattribute float aOpacity;\nuniform vec2 resolution;\nuniform vec2 dxDy;\nvarying vec2 vTexCoord;\nvarying float vOpacity;\nvoid main() {\n vTexCoord = aTexCoord;\n vOpacity = aOpacity;\n vec2 position = (aPosition + dxDy) / resolution * 2.0 - 1.0;\n gl_Position = vec4(position, 0.0, 1.0);\n}",frag:"precision mediump float;\nuniform sampler2D sourceTexture;\nuniform float layerOpacity;\nvarying vec2 vTexCoord;\nvarying float vOpacity;\nvoid main() {\n vec4 color = texture2D(sourceTexture, vTexCoord);\n gl_FragColor = color * vOpacity * layerOpacity;\n}",wgslVert:"struct VertexInput {\n @location($i) aPosition: vec2f,\n @location($i) aTexCoord: vec2f,\n @location($i) aOpacity: f32,\n};\nstruct VertexOutput {\n @builtin(position) Position: vec4f,\n @location($o) vTexCoord: vec2f,\n @location($o) vOpacity: f32,\n};\nstruct MyAppUniforms {\n resolution: vec2f,\n dxDy: vec2f,\n};\n@group(0) @binding($b) var uniforms: MyAppUniforms;\n@vertex\nfn main(vertexInput: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n output.vTexCoord = vertexInput.aTexCoord;\n output.vOpacity = vertexInput.aOpacity;\n let position = (vertexInput.aPosition + uniforms.dxDy) / uniforms.resolution * 2.0 - 1.0;\n output.Position = vec4f(position, 0.0, 1.0);\n return output;\n}",wgslFrag:"struct FragmentInput {\n @location($i) vTexCoord: vec2f,\n @location($i) vOpacity: f32,\n};\nstruct MyAppUniforms {\n layerOpacity: f32,\n};\n@group(0) @binding($b) var uniforms: MyAppUniforms;\n@group(0) @binding($b) var sourceTexture: texture_2d;\n@group(0) @binding($b) var sourceTextureSampler: sampler;\n@fragment\nfn main(fragmentInput: FragmentInput) -> @location(0) vec4f {\n let color = textureSample(sourceTexture, sourceTextureSampler, fragmentInput.vTexCoord);\n return color * fragmentInput.vOpacity * uniforms.layerOpacity;\n}",extraCommandProps:t})}_initClusterMeshes(){this.maxPointCount=1024,this.pointCount=0,this.clusterSprites={},this.clusterTextSprites={},this.sprites=[],this.textSprites=[],this.spriteCluster=[];const{positionBufferData:t,texCoordBufferData:r,opacityBufferData:i,textPositionData:s,textTexCoordData:o}=this._initBuffers();this.positionBufferData=t,this.texCoordBufferData=r,this.opacityBufferData=i,this.textPositionData=s,this.textTexCoordData=o,this._clusterGeometry=new e.Geometry({aPosition:this.positionBufferData,aTexCoord:this.texCoordBufferData,aOpacity:this.opacityBufferData},null,0,{positionSize:2}),this._clusterGeometry.generateBuffers(this.device),this._clusterMesh=new e.Mesh(this._clusterGeometry),this._scene=new e.Scene([this._clusterMesh]),this._textGeometry=new e.Geometry({aPosition:this.textPositionData,aTexCoord:this.textTexCoordData,aOpacity:this.opacityBufferData},null,0,{positionSize:2}),this._textGeometry.generateBuffers(this.device),this._textMesh=new e.Mesh(this._textGeometry),this._textScene=new e.Scene([this._textMesh]),this._renderer=new e.Renderer(this.device)}_initBuffers(){return{positionBufferData:new Float32Array(2*this.maxPointCount*6),texCoordBufferData:new Float32Array(2*this.maxPointCount*6),opacityBufferData:new Float32Array(1*this.maxPointCount*6),textPositionData:new Float32Array(2*this.maxPointCount*6),textTexCoordData:new Float32Array(2*this.maxPointCount*6)}}_getLayerOpacity(){let e=this.layer&&this.layer.options.opacity;return t.Util.isNil(e)&&(e=1),e}}u.registerRenderer("gl",a),u.registerRenderer("gpu",a)}export{u as ClusterLayer};"undefined"!=typeof console&&console.log("maptalks.markercluster v0.9.0"); 7 | //# sourceMappingURL=maptalks.markercluster.es.js.map 8 | -------------------------------------------------------------------------------- /dist/maptalks.markercluster.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * maptalks.markercluster v0.9.0 3 | * LICENSE : MIT 4 | * (c) 2016-2025 maptalks.org 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("maptalks"),require("@maptalks/gl"),require("@maptalks/vt")):"function"==typeof define&&define.amd?define(["exports","maptalks","@maptalks/gl","@maptalks/vt"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).maptalks=t.maptalks||{},t.maptalks,t.maptalks,t.maptalks)}(this,function(t,e,r,i){"use strict";function s(t){if(t&&t.__esModule)return t;var e=Object.create(null);return t&&Object.keys(t).forEach(function(r){if("default"!==r){var i=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,i.get?i:{enumerable:!0,get:function(){return t[r]}})}}),e.default=t,Object.freeze(e)}var o=s(e);const a=document.createElement("canvas").getContext("2d"),n=new o.Point(0,0),h=o.DrawToolLayer.markerLayerClazz;let u="canvas";h.getRendererClass("canvas")||(u="gl");const d={"renderer":u,"maxClusterRadius":160,"textSumProperty":null,"symbol":null,"drawClusterText":!0,"textSymbol":null,"animation":!0,"animationDuration":450,"maxClusterZoom":null,"noClusterWithOneMarker":!0,"forceRenderOnZooming":!0};class l extends h{static fromJSON(t){if(!t||"ClusterLayer"!==t.type)return null;const e=new l(t.id,t.options),r=t.geometries,i=[];for(let t=0;ti?super.identify(t,e):this._getRenderer()?this._getRenderer().identify(t,e):null}toJSON(){const t=super.toJSON.call(this);return t.type="ClusterLayer",t}getClusters(){const t=this._getRenderer();return t&&t._currentClusters||[]}}l.mergeOptions(d),l.registerJSONType("ClusterLayer");const c={"textFaceName":'"microsoft yahei"',"textSize":16,"textDx":0,"textDy":0},x={"markerType":"ellipse","markerFill":{property:"count",type:"interval",stops:[[0,"rgb(135, 196, 240)"],[9,"#1bbc9b"],[99,"rgb(216, 115, 149)"]]},"markerFillOpacity":.7,"markerLineOpacity":1,"markerLineWidth":3,"markerLineColor":"#fff","markerWidth":{property:"count",type:"interval",stops:[[0,40],[9,60],[99,80]]},"markerHeight":{property:"count",type:"interval",stops:[[0,40],[9,60],[99,80]]}},p=function(t){return class extends t{init(){this._refreshStyle(),this._clusterNeedRedraw=!0}checkResources(){if(!super.checkResources)return[];const t=this.layer.options.symbol||x,e=super.checkResources.apply(this,arguments);if(t!==this._symbolResourceChecked){const r=o.Util.getExternalResources(t,!0);r&&e.push.apply(e,r),this._symbolResourceChecked=t}return e}draw(){this.canvas||this.prepareCanvas();const t=this.getMap().getZoom(),e=this.layer.options.maxClusterZoom;if(e&&t>e)return delete this._currentClusters,this.checkMarksToDraw(),void super.draw.apply(this,arguments);let r;if(this._clusterNeedRedraw&&(this._clearDataCache(),this._computeGrid(),this._clusterNeedRedraw=!1),this._triggerAnimate&&this._startAnimation(t),this._animateDelta)r=this._animateClusters;else{const e=this._clusterCache[t]?this._clusterCache[t].clusters:null;r=this.getClustersToDraw(e),r.zoom=t}this._drawLayer(r)}_startAnimation(t){const e=this._clusterCache[t]?this._clusterCache[t].clusters:null,r=this.getClustersToDraw(e);r.zoom=t,this._animateClusters=r,this._parentClusters=this._currentClusters||r;const i=this.layer;if(i.options.animation&&this._triggerAnimate){let t=[0,1];"in"===this._inout&&(t=[1,0]),this._animateDelta=t[0],this._player=o.animation.Animation.animate({"d":t},{"speed":i.options.animationDuration,"easing":"inAndOut"},t=>{this._animateDelta=t.styles.d,"finished"===t.state.playState&&(delete this._animateDelta,delete this._inout,delete this._animateClusters,delete this._parentClusters),this.setToRedraw()}).play(),this.setToRedraw()}this._triggerAnimate=!1}checkMarksToDraw(){const t=this._markersToDraw!==this.layer._geoList;this._markersToDraw=this.layer._geoList,this._markersToDraw.dirty=t}getClustersToDraw(t){const e=this._markersToDraw||[];this._markersToDraw=[];const r=this.getMap(),i=o.StringUtil.getFont(this._textSymbol),s=o.StringUtil.stringLength("9",i).toPoint(),a=r.getContainerExtent(),n=[];let h,u,d,l,c,x=0,p=!1;for(const i in t){if(this._currentGrid=t[i],1===t[i].count&&this.layer.options.noClusterWithOneMarker){const r=t[i].children[0];r._cluster=t[i],p||e[x++]===r||(p=!0),this._markersToDraw.push(r);continue}if(d=this._getSprite().sprite,l=d.canvas.width,c=d.canvas.height,h=r._prjToContainerPoint(t[i].center),u=new o.PointExtent(h.sub(l,c),h.add(l,c)),a.intersects(u)){if(!t[i].textSize){const e=this._getClusterText(t[i]);t[i].textSize=new o.Point(s.x*e.length,s.y)._multi(.5)}n.push(t[i])}}return e.length!==this._markersToDraw.length&&(p=!0),this._markersToDraw.dirty=p,n}drawOnInteracting(...t){this._currentClusters&&this.drawClusters(this._currentClusters,1),super.drawOnInteracting(...t)}getCurrentNeedRenderGeos(){return this._markersToDraw?this._markersToDraw:[]}_getCurrentNeedRenderGeos(){return this.getCurrentNeedRenderGeos()}forEachGeo(t,e){this._markersToDraw&&this._markersToDraw.forEach(r=>{e?t.call(e,r):t(r)})}onGeometryShow(){this._clusterNeedRedraw=!0,super.onGeometryShow.apply(this,arguments)}onGeometryHide(){this._clusterNeedRedraw=!0,super.onGeometryHide.apply(this,arguments)}onGeometryAdd(){this._clusterNeedRedraw=!0,super.onGeometryAdd.apply(this,arguments)}onGeometryRemove(){this._clusterNeedRedraw=!0,super.onGeometryRemove.apply(this,arguments)}onGeometryPositionChange(){this._clusterNeedRedraw=!0,super.onGeometryPositionChange.apply(this,arguments)}onRemove(){this._clearDataCache()}identify(t,e){const r=this.getMap(),i=this.layer.options.maxClusterZoom;if(i&&r.getZoom()>i)return super.identify(t,e);if(this._currentClusters){const e=r.coordinateToContainerPoint(t),i=this._currentGrid;for(let t=0;t[this.getMap().getZoom(),this._currentGrid];this._symbol=o.MapboxUtil.loadFunctionTypes(t,r),this._textSymbol=o.MapboxUtil.loadFunctionTypes(e,r)}_drawLayer(t){this._currentClusters=t,this._animateDelta>=0?"in"===this._inout?this.drawClustersFrame(t,this._parentClusters,this._animateDelta):this.drawClustersFrame(this._parentClusters,t,this._animateDelta):this.drawClusters(t,1),this.drawMarkers(),this.completeRender()}drawMarkers(){super.drawGeos()}drawClustersFrame(t,e,r){this.prepareCanvas();const i=this.getMap(),s={};if(t&&t.forEach(t=>{const e=i._prjToContainerPoint(t.center);s[t.key]||(s[t.key]=1,this.drawCluster(e,t,1-r))}),0===r||!e)return;const o=t.zoom,a=i._getResolution(o)*this.layer.options.maxClusterRadius,n=this._markerExtent.getMin();e.forEach(t=>{let e=i._prjToContainerPoint(t.center);const s=t.center,h=Math.floor((s.x-n.x)/a)+"_"+Math.floor((s.y-n.y)/a),u=this._clusterCache[o]?this._clusterCache[o].clusterMap[h]:null;if(u){const t=i._prjToContainerPoint(u.center);e=t.add(e.sub(t)._multi(r))}this.drawCluster(e,t,r>.5?1:r)})}drawClusters(t,e){if(!t)return;this.prepareCanvas();const r=this.getMap();t.forEach(t=>{const i=r._prjToContainerPoint(t.center);this.drawCluster(i,t,e>.5?1:e)})}drawCluster(t,e,r){this._currentGrid=e;const i=this.context,s=this._getSprite().sprite,a=i.globalAlpha;if(a*r!==0){if(i.globalAlpha=a*r,s){const e=t.add(s.offset)._sub(s.canvas.width/2,s.canvas.height/2);o.Canvas.image(i,s.canvas,e.x,e.y)}if(this.layer.options.drawClusterText&&e.textSize){o.Canvas.prepareCanvasFont(i,this._textSymbol),i.textBaseline="middle";const r=this._textSymbol.textDx||0,s=this._textSymbol.textDy||0,a=this._getClusterText(e);o.Canvas.fillText(i,a,t.sub(e.textSize.x,0)._add(r,s))}i.globalAlpha=a}}_getClusterText(t){return(this.layer.options.textSumProperty?t.textSumProperty:t.count)+""}_getSprite(){this._spriteCache||(this._spriteCache={});const t=function(t){const e=[];for(const r in t)"_"!==r[0]&&e.push(t[r]);return e.join("|")}(this._symbol);return this._spriteCache[t]||(this._spriteCache[t]=new o.Marker([0,0],{"symbol":this._symbol})._getSprite(this.resources,this.getMap().CanvasClass)),{sprite:this._spriteCache[t],key:t}}_initGridSystem(){const t=[];let e,r;this.layer.forEach(i=>{i.isVisible()&&(r=i._getPrjCoordinates(),e=e?e._combine(i._getPrjExtent()):i._getPrjExtent(),t.push({x:r.x,y:r.y,id:i._getInternalId(),geometry:i}))}),this._markerExtent=e,this._markerPoints=t}_computeGrid(){const t=this.getMap(),e=t.getZoom();this._markerExtent||this._initGridSystem(),this._clusterCache||(this._clusterCache={});const r=t._getResolution(t.getMinZoom())>t._getResolution(t.getMaxZoom())?e-1:e+1;this._clusterCache[r]&&this._clusterCache[r].length===this.layer.getCount()&&(this._clusterCache[e]=this._clusterCache[r]),this._clusterCache[e]||(this._clusterCache[e]=this._computeZoomGrid(e))}_computeZoomGrid(t){if(!this._markerExtent)return null;const e=this.getMap(),r=e._getResolution(t)*this.layer.options.maxClusterRadius,i=e._getResolution(t-1)?e._getResolution(t-1)*this.layer.options.maxClusterRadius:null;let s=this._clusterCache[t-1];!s&&t-1>=e.getMinZoom()&&(this._clusterCache[t-1]=s=this._computeZoomGrid(t-1));const a=this._markerPoints,n=this.layer.options.textSumProperty,h={},u=this._markerExtent.getMin();let d,l,c,x,p,y;for(let t=0,e=a.length;tt.to?"in":"out",this._triggerAnimate=!0,this._computeGrid(),super.onZoomEnd.apply(this,arguments)):super.onZoomEnd.apply(this,arguments)}_clearDataCache(){this._stopAnim(),delete this._markerExtent,delete this._markerPoints,delete this._clusterCache,delete this._zoomInClusters}}};class y extends(p(o.renderer.VectorLayerCanvasRenderer)){constructor(...t){super(...t),this.init()}}if(l.registerRenderer("canvas",y),void 0!==i.PointLayerRenderer){class t extends(p(i.PointLayerRenderer)){constructor(...t){super(...t),this.init()}drawOnInteracting(t,e,r){this._currentClusters&&this.drawClusters(this._currentClusters,1),this.drawMarkers(),i.PointLayerRenderer.prototype.draw.call(this,e,r)}drawClusters(...t){this._clearToDraw(),super.drawClusters(...t),this.flush()}drawClustersFrame(...t){this._clearToDraw(),super.drawClustersFrame(...t),this.flush()}_clearToDraw(){this.pointCount=0,this.bufferIndex=0,this.opacityIndex=0,this.textIndex=0}drawCluster(t,e,r){this._currentGrid=e;const{sprite:i,key:s}=this._getSprite(),n=i.canvas;i.data||(i.data=n.getContext("2d",{willReadFrequently:!0}).getImageData(0,0,n.width,n.height)),this.clusterSprites[s]||(this.clusterSprites[s]=i,this.textureDirty=!0);const h=t.add(i.offset)._sub(n.width/2,n.height/2);let u=h.x,d=h.y;const l=this.getMap(),c=l.getDevicePixelRatio();u*=c,d=(l.height-d)*c;const x=i.data.width*c,p=i.data.height*c;if(this.addPoint(u,d,x,p,r,s),this.layer.options.drawClusterText){o.Canvas.prepareCanvasFont(a,this._textSymbol);const t=a.font+"-"+a.fillStyle,r=this._getClusterText(e),{sprite:i,key:s}=this._getTextSprite(r,t);this.clusterTextSprites[s]||(this.clusterTextSprites[s]=i,this.textTextureDirty=!0),this.addTextPoint(u+x/2,d-p/2,i.data.width*c,i.data.height*c,s)}this.pointCount++}_getTextSprite(t,e){this._textSpriteCache||(this._textSpriteCache={});const r=e+"-"+t;if(!this._textSpriteCache[r]){const e=this.getMap().getDevicePixelRatio(),i=a.measureText(t),s=i.width,h=i.actualBoundingBoxAscent+i.actualBoundingBoxDescent,u=document.createElement("canvas");u.width=s*e,u.height=h*e;const d=u.getContext("2d",{willReadFrequently:!0});d.scale(e,e),o.Canvas.prepareCanvasFont(d,this._textSymbol),d.textBaseline="top",d.fillText(t,0,0);const l=document.getElementById("debug-text-sprite");if(l){l.width=u.width,l.height=u.height;l.getContext("2d").drawImage(u,0,0)}this._textSpriteCache[r]={canvas:u,offset:n,data:d.getImageData(0,0,u.width,u.height)}}return{sprite:this._textSpriteCache[r],key:r}}checkMarksToDraw(){super.checkMarksToDraw(),this.drawMarkers()}drawMarkers(){this._markersToDraw.dirty&&(this.rebuildGeometries(),this._markersToDraw.dirty=!1)}flush(t){if(0===this.pointCount)return;this._updateMesh();const e=t&&t.renderTarget&&context.renderTarget.fbo;this._clusterGeometry.setDrawCount(6*this.pointCount);const{width:r,height:i}=this.canvas,s={resolution:[r,i],layerOpacity:this._getLayerOpacity(),dxDy:[0,0]};if(this._renderer.render(this._spriteShader,s,this._scene,e),this.layer.options.drawClusterText){this._textGeometry.setDrawCount(6*this.pointCount);const t=this._textSymbol.textDx||0,r=this._textSymbol.textDy||0;s.dxDy=[t,r],this._renderer.render(this._spriteShader,s,this._textScene,e)}}_updateMesh(){const t=this.textureDirty,e=this._genAtlas();if(this._updateTexCoord(e,t),this.layer.options.drawClusterText){const t=this.textTextureDirty,e=this._genTextAtlas();this._updateTextTexCoord(e,t)}this._updateGeometryData()}addPoint(t,e,r,i,s,o){this._check();const a=r,n=i;this.addVertex(t,e-n,s),this.addVertex(t+a,e-n,s),this.addVertex(t,e,s),this.addVertex(t,e,s),this.addVertex(t+a,e-n,s),this.addVertex(t+a,e,s),this.sprites[this.pointCount]!==o&&(this.sprites[this.pointCount]=o,this.sprites.dirty=!0)}addVertex(t,e,r){const i=this.positionBufferData;i[this.bufferIndex]!==t&&(i[this.bufferIndex]=t,i.dirty=!0),this.bufferIndex++,i[this.bufferIndex]!==e&&(i[this.bufferIndex]=e,i.dirty=!0),this.bufferIndex++;const s=this.opacityBufferData;s[this.opacityIndex]!==r&&(s[this.opacityIndex]=r,s.dirty=!0),this.opacityIndex++}addTextPoint(t,e,r,i,s){this._check();const o=this.getMap().getDevicePixelRatio(),a=(r/=o)/2,n=(i/=o)/2;this.addTextVertex(t-a,e-n),this.addTextVertex(t+a,e-n),this.addTextVertex(t-a,e+n),this.addTextVertex(t-a,e+n),this.addTextVertex(t+a,e-n),this.addTextVertex(t+a,e+n),this.textSprites[this.pointCount]!==s&&(this.textSprites[this.pointCount]=s,this.textSprites.dirty=!0)}addTextVertex(t,e){const r=this.textPositionData;r[this.textIndex]!==t&&(r[this.textIndex]=t,r.dirty=!0),this.textIndex++,r[this.textIndex]!==e&&(r[this.textIndex]=e,r.dirty=!0),this.textIndex++}_check(){if(this.pointCount>=this.maxPointCount-1){this.maxPointCount+=1024;const{positionBufferData:t,texCoordBufferData:e,opacityBufferData:r,textPositionData:i,textTexCoordData:s}=this._initBuffers();for(let r=0;rthis.canvas?this.canvas.width:1,height:()=>this.canvas?this.canvas.height:1},depth:{enable:!1},blend:{enable:!0,func:{src:1,dst:"one minus src alpha"}}};this._spriteShader=new r.reshader.MeshShader({name:"cluster-sprite",vert:"attribute vec2 aPosition;\nattribute vec2 aTexCoord;\nattribute float aOpacity;\nuniform vec2 resolution;\nuniform vec2 dxDy;\nvarying vec2 vTexCoord;\nvarying float vOpacity;\nvoid main() {\n vTexCoord = aTexCoord;\n vOpacity = aOpacity;\n vec2 position = (aPosition + dxDy) / resolution * 2.0 - 1.0;\n gl_Position = vec4(position, 0.0, 1.0);\n}",frag:"precision mediump float;\nuniform sampler2D sourceTexture;\nuniform float layerOpacity;\nvarying vec2 vTexCoord;\nvarying float vOpacity;\nvoid main() {\n vec4 color = texture2D(sourceTexture, vTexCoord);\n gl_FragColor = color * vOpacity * layerOpacity;\n}",wgslVert:"struct VertexInput {\n @location($i) aPosition: vec2f,\n @location($i) aTexCoord: vec2f,\n @location($i) aOpacity: f32,\n};\nstruct VertexOutput {\n @builtin(position) Position: vec4f,\n @location($o) vTexCoord: vec2f,\n @location($o) vOpacity: f32,\n};\nstruct MyAppUniforms {\n resolution: vec2f,\n dxDy: vec2f,\n};\n@group(0) @binding($b) var uniforms: MyAppUniforms;\n@vertex\nfn main(vertexInput: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n output.vTexCoord = vertexInput.aTexCoord;\n output.vOpacity = vertexInput.aOpacity;\n let position = (vertexInput.aPosition + uniforms.dxDy) / uniforms.resolution * 2.0 - 1.0;\n output.Position = vec4f(position, 0.0, 1.0);\n return output;\n}",wgslFrag:"struct FragmentInput {\n @location($i) vTexCoord: vec2f,\n @location($i) vOpacity: f32,\n};\nstruct MyAppUniforms {\n layerOpacity: f32,\n};\n@group(0) @binding($b) var uniforms: MyAppUniforms;\n@group(0) @binding($b) var sourceTexture: texture_2d;\n@group(0) @binding($b) var sourceTextureSampler: sampler;\n@fragment\nfn main(fragmentInput: FragmentInput) -> @location(0) vec4f {\n let color = textureSample(sourceTexture, sourceTextureSampler, fragmentInput.vTexCoord);\n return color * fragmentInput.vOpacity * uniforms.layerOpacity;\n}",extraCommandProps:t})}_initClusterMeshes(){this.maxPointCount=1024,this.pointCount=0,this.clusterSprites={},this.clusterTextSprites={},this.sprites=[],this.textSprites=[],this.spriteCluster=[];const{positionBufferData:t,texCoordBufferData:e,opacityBufferData:i,textPositionData:s,textTexCoordData:o}=this._initBuffers();this.positionBufferData=t,this.texCoordBufferData=e,this.opacityBufferData=i,this.textPositionData=s,this.textTexCoordData=o,this._clusterGeometry=new r.reshader.Geometry({aPosition:this.positionBufferData,aTexCoord:this.texCoordBufferData,aOpacity:this.opacityBufferData},null,0,{positionSize:2}),this._clusterGeometry.generateBuffers(this.device),this._clusterMesh=new r.reshader.Mesh(this._clusterGeometry),this._scene=new r.reshader.Scene([this._clusterMesh]),this._textGeometry=new r.reshader.Geometry({aPosition:this.textPositionData,aTexCoord:this.textTexCoordData,aOpacity:this.opacityBufferData},null,0,{positionSize:2}),this._textGeometry.generateBuffers(this.device),this._textMesh=new r.reshader.Mesh(this._textGeometry),this._textScene=new r.reshader.Scene([this._textMesh]),this._renderer=new r.reshader.Renderer(this.device)}_initBuffers(){return{positionBufferData:new Float32Array(2*this.maxPointCount*6),texCoordBufferData:new Float32Array(2*this.maxPointCount*6),opacityBufferData:new Float32Array(1*this.maxPointCount*6),textPositionData:new Float32Array(2*this.maxPointCount*6),textTexCoordData:new Float32Array(2*this.maxPointCount*6)}}_getLayerOpacity(){let t=this.layer&&this.layer.options.opacity;return o.Util.isNil(t)&&(t=1),t}}l.registerRenderer("gl",t),l.registerRenderer("gpu",t)}t.ClusterLayer=l,Object.defineProperty(t,"__esModule",{value:!0}),"undefined"!=typeof console&&console.log("maptalks.markercluster v0.9.0")}); 7 | //# sourceMappingURL=maptalks.markercluster.js.map 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import * as maptalks from 'maptalks'; 2 | import { reshader } from '@maptalks/gl'; 3 | import { getVectorPacker, PointLayerRenderer } from '@maptalks/vt'; 4 | import vert from './glsl/sprite.vert'; 5 | import frag from './glsl/sprite.frag'; 6 | import wgslVert from './wgsl/sprite_vert.wgsl'; 7 | import wgslFrag from './wgsl/sprite_frag.wgsl'; 8 | 9 | const FONT_CANVAS = document.createElement('canvas'); 10 | const fontCtx = FONT_CANVAS.getContext('2d'); 11 | const ZERO_POINT = new maptalks.Point(0, 0); 12 | 13 | const MarkerLayerClazz = maptalks.DrawToolLayer.markerLayerClazz; 14 | let renderer = 'canvas'; 15 | const RendererClazz = MarkerLayerClazz.getRendererClass('canvas'); 16 | if (!RendererClazz) { 17 | renderer = 'gl'; 18 | } 19 | 20 | const options = { 21 | 'renderer': renderer, 22 | 'maxClusterRadius' : 160, 23 | 'textSumProperty' : null, 24 | 'symbol' : null, 25 | 'drawClusterText' : true, 26 | 'textSymbol' : null, 27 | 'animation' : true, 28 | 'animationDuration' : 450, 29 | 'maxClusterZoom' : null, 30 | 'noClusterWithOneMarker':true, 31 | 'forceRenderOnZooming' : true 32 | }; 33 | 34 | export class ClusterLayer extends MarkerLayerClazz { 35 | /** 36 | * Reproduce a ClusterLayer from layer's profile JSON. 37 | * @param {Object} json - layer's profile JSON 38 | * @return {maptalks.ClusterLayer} 39 | * @static 40 | * @private 41 | * @function 42 | */ 43 | static fromJSON(json) { 44 | if (!json || json['type'] !== 'ClusterLayer') { return null; } 45 | const layer = new ClusterLayer(json['id'], json['options']); 46 | const geoJSONs = json['geometries']; 47 | const geometries = []; 48 | for (let i = 0; i < geoJSONs.length; i++) { 49 | const geo = maptalks.Geometry.fromJSON(geoJSONs[i]); 50 | if (geo) { 51 | geometries.push(geo); 52 | } 53 | } 54 | layer.addGeometry(geometries); 55 | return layer; 56 | } 57 | 58 | addMarker(markers) { 59 | return this.addGeometry(markers); 60 | } 61 | 62 | addGeometry(markers) { 63 | for (let i = 0, len = markers.length; i < len; i++) { 64 | if (!(markers[i] instanceof maptalks.Marker)) { 65 | throw new Error('Only a point(Marker) can be added into a ClusterLayer'); 66 | } 67 | } 68 | return super.addGeometry.apply(this, arguments); 69 | } 70 | 71 | onConfig(conf) { 72 | super.onConfig(conf); 73 | if (conf['maxClusterRadius'] || 74 | conf['symbol'] || 75 | conf['drawClusterText'] || 76 | conf['textSymbol'] || 77 | conf['maxClusterZoom']) { 78 | const renderer = this._getRenderer(); 79 | if (renderer) { 80 | renderer.render(); 81 | } 82 | } 83 | return this; 84 | } 85 | 86 | /** 87 | * Identify the clusters on the given coordinate 88 | * @param {maptalks.Coordinate} coordinate - coordinate to identify 89 | * @return {Object|Geometry[]} result: cluster { center : [cluster's center], children : [geometries in the cluster] } or markers 90 | */ 91 | identify(coordinate, options) { 92 | const map = this.getMap(), 93 | maxZoom = this.options['maxClusterZoom']; 94 | if (maxZoom && map && map.getZoom() > maxZoom) { 95 | return super.identify(coordinate, options); 96 | } 97 | if (this._getRenderer()) { 98 | return this._getRenderer().identify(coordinate, options); 99 | } 100 | return null; 101 | } 102 | 103 | /** 104 | * Export the ClusterLayer's JSON. 105 | * @return {Object} layer's JSON 106 | */ 107 | toJSON() { 108 | const json = super.toJSON.call(this); 109 | json['type'] = 'ClusterLayer'; 110 | return json; 111 | } 112 | /** 113 | * Get the ClusterLayer's current clusters 114 | * @return {Object} layer's clusters 115 | **/ 116 | getClusters() { 117 | const renderer = this._getRenderer(); 118 | if (renderer) { 119 | return renderer._currentClusters || []; 120 | } 121 | return []; 122 | } 123 | } 124 | 125 | // merge to define ClusterLayer's default options. 126 | ClusterLayer.mergeOptions(options); 127 | 128 | // register ClusterLayer's JSON type for JSON deserialization. 129 | ClusterLayer.registerJSONType('ClusterLayer'); 130 | 131 | const defaultTextSymbol = { 132 | 'textFaceName' : '"microsoft yahei"', 133 | 'textSize' : 16, 134 | 'textDx' : 0, 135 | 'textDy' : 0 136 | }; 137 | 138 | const defaultSymbol = { 139 | 'markerType' : 'ellipse', 140 | 'markerFill' : { property:'count', type:'interval', stops: [[0, 'rgb(135, 196, 240)'], [9, '#1bbc9b'], [99, 'rgb(216, 115, 149)']] }, 141 | 'markerFillOpacity' : 0.7, 142 | 'markerLineOpacity' : 1, 143 | 'markerLineWidth' : 3, 144 | 'markerLineColor' : '#fff', 145 | 'markerWidth' : { property:'count', type:'interval', stops: [[0, 40], [9, 60], [99, 80]] }, 146 | 'markerHeight' : { property:'count', type:'interval', stops: [[0, 40], [9, 60], [99, 80]] } 147 | }; 148 | 149 | const ClusterLayerRenderable = function(Base) { 150 | const renderable = class extends Base { 151 | init() { 152 | this._refreshStyle(); 153 | this._clusterNeedRedraw = true; 154 | } 155 | 156 | checkResources() { 157 | if (!super.checkResources) { 158 | return []; 159 | } 160 | const symbol = this.layer.options['symbol'] || defaultSymbol; 161 | const resources = super.checkResources.apply(this, arguments); 162 | if (symbol !== this._symbolResourceChecked) { 163 | const res = maptalks.Util.getExternalResources(symbol, true); 164 | if (res) { 165 | resources.push.apply(resources, res); 166 | } 167 | this._symbolResourceChecked = symbol; 168 | } 169 | return resources; 170 | } 171 | 172 | draw() { 173 | if (!this.canvas) { 174 | this.prepareCanvas(); 175 | } 176 | const map = this.getMap(); 177 | const zoom = map.getZoom(); 178 | const maxClusterZoom = this.layer.options['maxClusterZoom']; 179 | if (maxClusterZoom && zoom > maxClusterZoom) { 180 | delete this._currentClusters; 181 | this.checkMarksToDraw(); 182 | super.draw.apply(this, arguments); 183 | return; 184 | } 185 | if (this._clusterNeedRedraw) { 186 | this._clearDataCache(); 187 | this._computeGrid(); 188 | this._clusterNeedRedraw = false; 189 | } 190 | let clusters; 191 | if (this._triggerAnimate) { 192 | this._startAnimation(zoom); 193 | } 194 | if (this._animateDelta) { 195 | clusters = this._animateClusters; 196 | } else { 197 | const zoomClusters = this._clusterCache[zoom] ? this._clusterCache[zoom]['clusters'] : null; 198 | clusters = this.getClustersToDraw(zoomClusters); 199 | clusters.zoom = zoom; 200 | } 201 | this._drawLayer(clusters); 202 | } 203 | 204 | _startAnimation(zoom) { 205 | const zoomClusters = this._clusterCache[zoom] ? this._clusterCache[zoom]['clusters'] : null; 206 | const clusters = this.getClustersToDraw(zoomClusters); 207 | clusters.zoom = zoom; 208 | 209 | this._animateClusters = clusters; 210 | this._parentClusters = this._currentClusters || clusters; 211 | const layer = this.layer; 212 | if (layer.options['animation'] && this._triggerAnimate) { 213 | let dr = [0, 1]; 214 | if (this._inout === 'in') { 215 | dr = [1, 0]; 216 | } 217 | this._animateDelta = dr[0]; 218 | this._player = maptalks.animation.Animation.animate( 219 | { 'd' : dr }, 220 | { 'speed' : layer.options['animationDuration'], 'easing' : 'inAndOut' }, 221 | frame => { 222 | this._animateDelta = frame.styles.d; 223 | if (frame.state.playState === 'finished') { 224 | delete this._animateDelta; 225 | delete this._inout; 226 | delete this._animateClusters; 227 | delete this._parentClusters 228 | } 229 | this.setToRedraw(); 230 | } 231 | ) 232 | .play(); 233 | this.setToRedraw(); 234 | } 235 | this._triggerAnimate = false; 236 | } 237 | 238 | checkMarksToDraw() { 239 | const dirty = this._markersToDraw !== this.layer._geoList; 240 | this._markersToDraw = this.layer._geoList; 241 | this._markersToDraw.dirty = dirty; 242 | } 243 | 244 | getClustersToDraw(zoomClusters) { 245 | const oldMarkersToDraw = this._markersToDraw || []; 246 | this._markersToDraw = []; 247 | const map = this.getMap(); 248 | const font = maptalks.StringUtil.getFont(this._textSymbol), 249 | digitLen = maptalks.StringUtil.stringLength('9', font).toPoint(); 250 | const extent = map.getContainerExtent(), 251 | clusters = []; 252 | let pt, pExt, sprite, width, height, markerIndex = 0, isMarkerDirty = false; 253 | for (const p in zoomClusters) { 254 | this._currentGrid = zoomClusters[p]; 255 | if (zoomClusters[p]['count'] === 1 && this.layer.options['noClusterWithOneMarker']) { 256 | const marker = zoomClusters[p]['children'][0]; 257 | marker._cluster = zoomClusters[p]; 258 | if (!isMarkerDirty && oldMarkersToDraw[markerIndex++] !== marker) { 259 | isMarkerDirty = true; 260 | } 261 | this._markersToDraw.push(marker); 262 | continue; 263 | } 264 | sprite = this._getSprite().sprite; 265 | width = sprite.canvas.width; 266 | height = sprite.canvas.height; 267 | pt = map._prjToContainerPoint(zoomClusters[p]['center']); 268 | pExt = new maptalks.PointExtent(pt.sub(width, height), pt.add(width, height)); 269 | if (!extent.intersects(pExt)) { 270 | continue; 271 | } 272 | if (!zoomClusters[p]['textSize']) { 273 | const text = this._getClusterText(zoomClusters[p]); 274 | zoomClusters[p]['textSize'] = new maptalks.Point(digitLen.x * text.length, digitLen.y)._multi(1 / 2); 275 | } 276 | clusters.push(zoomClusters[p]); 277 | } 278 | if (oldMarkersToDraw.length !== this._markersToDraw.length) { 279 | isMarkerDirty = true; 280 | } 281 | this._markersToDraw.dirty = isMarkerDirty; 282 | return clusters; 283 | } 284 | 285 | drawOnInteracting(...args) { 286 | if (this._currentClusters) { 287 | this.drawClusters(this._currentClusters, 1); 288 | } 289 | super.drawOnInteracting(...args); 290 | } 291 | 292 | getCurrentNeedRenderGeos() { 293 | if (this._markersToDraw) { 294 | return this._markersToDraw; 295 | } 296 | return []; 297 | } 298 | 299 | _getCurrentNeedRenderGeos() { 300 | return this.getCurrentNeedRenderGeos(); 301 | } 302 | 303 | forEachGeo(fn, context) { 304 | if (this._markersToDraw) { 305 | this._markersToDraw.forEach((g) => { 306 | if (context) { 307 | fn.call(context, g); 308 | } else { 309 | fn(g); 310 | } 311 | }); 312 | } 313 | } 314 | 315 | onGeometryShow() { 316 | this._clusterNeedRedraw = true; 317 | super.onGeometryShow.apply(this, arguments); 318 | } 319 | 320 | onGeometryHide() { 321 | this._clusterNeedRedraw = true; 322 | super.onGeometryHide.apply(this, arguments); 323 | } 324 | 325 | onGeometryAdd() { 326 | this._clusterNeedRedraw = true; 327 | super.onGeometryAdd.apply(this, arguments); 328 | } 329 | 330 | onGeometryRemove() { 331 | this._clusterNeedRedraw = true; 332 | super.onGeometryRemove.apply(this, arguments); 333 | } 334 | 335 | onGeometryPositionChange() { 336 | this._clusterNeedRedraw = true; 337 | super.onGeometryPositionChange.apply(this, arguments); 338 | } 339 | 340 | onRemove() { 341 | this._clearDataCache(); 342 | } 343 | 344 | identify(coordinate, options) { 345 | const map = this.getMap(), 346 | maxZoom = this.layer.options['maxClusterZoom']; 347 | if (maxZoom && map.getZoom() > maxZoom) { 348 | return super.identify(coordinate, options); 349 | } 350 | if (this._currentClusters) { 351 | const point = map.coordinateToContainerPoint(coordinate); 352 | const old = this._currentGrid; 353 | for (let i = 0; i < this._currentClusters.length; i++) { 354 | const c = this._currentClusters[i]; 355 | const pt = map._prjToContainerPoint(c['center']); 356 | this._currentGrid = c; 357 | const markerWidth = this._getSprite().sprite.canvas.width; 358 | 359 | if (point.distanceTo(pt) <= markerWidth) { 360 | return { 361 | 'center' : map.getProjection().unproject(c.center.copy()), 362 | 'children' : c.children.slice(0) 363 | }; 364 | } 365 | } 366 | this._currentGrid = old; 367 | } 368 | 369 | // if no clusters is hit, identify markers 370 | if (this._markersToDraw && this._markersToDraw[0]) { 371 | const point = map.coordinateToContainerPoint(coordinate); 372 | return this.layer._hitGeos(this._markersToDraw, point, options); 373 | } 374 | return null; 375 | } 376 | 377 | onSymbolChanged() { 378 | this._refreshStyle(); 379 | this._computeGrid(); 380 | this._stopAnim(); 381 | this.setToRedraw(); 382 | } 383 | 384 | _refreshStyle() { 385 | const symbol = this.layer.options['symbol'] || defaultSymbol; 386 | const textSymbol = this.layer.options['textSymbol'] || defaultTextSymbol; 387 | const argFn = () => [this.getMap().getZoom(), this._currentGrid]; 388 | this._symbol = maptalks.MapboxUtil.loadFunctionTypes(symbol, argFn); 389 | this._textSymbol = maptalks.MapboxUtil.loadFunctionTypes(textSymbol, argFn); 390 | } 391 | 392 | _drawLayer(clusters) { 393 | this._currentClusters = clusters; 394 | if (this._animateDelta >= 0) { 395 | if (this._inout === 'in') { 396 | this.drawClustersFrame(clusters, this._parentClusters, this._animateDelta); 397 | } else { 398 | this.drawClustersFrame(this._parentClusters, clusters, this._animateDelta); 399 | } 400 | } else { 401 | this.drawClusters(clusters, 1); 402 | } 403 | this.drawMarkers(); 404 | this.completeRender(); 405 | } 406 | 407 | drawMarkers() { 408 | super.drawGeos(); 409 | } 410 | 411 | drawClustersFrame(parentClusters, toClusters, ratio) { 412 | this.prepareCanvas(); 413 | const map = this.getMap(), 414 | drawn = {}; 415 | if (parentClusters) { 416 | parentClusters.forEach(c => { 417 | const p = map._prjToContainerPoint(c['center']); 418 | if (!drawn[c.key]) { 419 | drawn[c.key] = 1; 420 | this.drawCluster(p, c, 1 - ratio); 421 | } 422 | }); 423 | } 424 | if (ratio === 0 || !toClusters) { 425 | return; 426 | } 427 | const z = parentClusters.zoom, 428 | r = map._getResolution(z) * this.layer.options['maxClusterRadius'], 429 | min = this._markerExtent.getMin(); 430 | toClusters.forEach(c => { 431 | let pt = map._prjToContainerPoint(c['center']); 432 | const center = c.center; 433 | const pgx = Math.floor((center.x - min.x) / r), 434 | pgy = Math.floor((center.y - min.y) / r); 435 | const pkey = pgx + '_' + pgy; 436 | const parent = this._clusterCache[z] ? this._clusterCache[z]['clusterMap'][pkey] : null; 437 | if (parent) { 438 | const pp = map._prjToContainerPoint(parent['center']); 439 | pt = pp.add(pt.sub(pp)._multi(ratio)); 440 | } 441 | this.drawCluster(pt, c, ratio > 0.5 ? 1 : ratio); 442 | }); 443 | } 444 | 445 | drawClusters(clusters, ratio) { 446 | if (!clusters) { 447 | return; 448 | } 449 | this.prepareCanvas(); 450 | const map = this.getMap(); 451 | clusters.forEach(c => { 452 | const pt = map._prjToContainerPoint(c['center']); 453 | this.drawCluster(pt, c, ratio > 0.5 ? 1 : ratio); 454 | }); 455 | 456 | } 457 | 458 | drawCluster(pt, cluster, op) { 459 | this._currentGrid = cluster; 460 | const ctx = this.context; 461 | const sprite = this._getSprite().sprite; 462 | const opacity = ctx.globalAlpha; 463 | if (opacity * op === 0) { 464 | return; 465 | } 466 | ctx.globalAlpha = opacity * op; 467 | if (sprite) { 468 | const pos = pt.add(sprite.offset)._sub(sprite.canvas.width / 2, sprite.canvas.height / 2); 469 | maptalks.Canvas.image(ctx, sprite.canvas, pos.x, pos.y); 470 | } 471 | 472 | if (this.layer.options['drawClusterText'] && cluster['textSize']) { 473 | maptalks.Canvas.prepareCanvasFont(ctx, this._textSymbol); 474 | ctx.textBaseline = 'middle'; 475 | const dx = this._textSymbol['textDx'] || 0; 476 | const dy = this._textSymbol['textDy'] || 0; 477 | const text = this._getClusterText(cluster); 478 | maptalks.Canvas.fillText(ctx, text, pt.sub(cluster['textSize'].x, 0)._add(dx, dy)); 479 | } 480 | ctx.globalAlpha = opacity; 481 | } 482 | 483 | _getClusterText(cluster) { 484 | const text = this.layer.options['textSumProperty'] ? cluster['textSumProperty'] : cluster['count']; 485 | return text + ''; 486 | } 487 | 488 | _getSprite() { 489 | if (!this._spriteCache) { 490 | this._spriteCache = {}; 491 | } 492 | const key = getSymbolStamp(this._symbol); 493 | if (!this._spriteCache[key]) { 494 | this._spriteCache[key] = new maptalks.Marker([0, 0], { 'symbol' : this._symbol })._getSprite(this.resources, this.getMap().CanvasClass); 495 | } 496 | return { 497 | sprite: this._spriteCache[key], 498 | key 499 | }; 500 | } 501 | 502 | _initGridSystem() { 503 | const points = []; 504 | let extent, c; 505 | this.layer.forEach(g => { 506 | if (!g.isVisible()) { 507 | return; 508 | } 509 | c = g._getPrjCoordinates(); 510 | if (!extent) { 511 | extent = g._getPrjExtent(); 512 | } else { 513 | extent = extent._combine(g._getPrjExtent()); 514 | } 515 | points.push({ 516 | x : c.x, 517 | y : c.y, 518 | id : g._getInternalId(), 519 | geometry : g 520 | }); 521 | }); 522 | this._markerExtent = extent; 523 | this._markerPoints = points; 524 | } 525 | 526 | _computeGrid() { 527 | const map = this.getMap(), 528 | zoom = map.getZoom(); 529 | if (!this._markerExtent) { 530 | this._initGridSystem(); 531 | } 532 | if (!this._clusterCache) { 533 | this._clusterCache = {}; 534 | } 535 | const pre = map._getResolution(map.getMinZoom()) > map._getResolution(map.getMaxZoom()) ? zoom - 1 : zoom + 1; 536 | if (this._clusterCache[pre] && this._clusterCache[pre].length === this.layer.getCount()) { 537 | this._clusterCache[zoom] = this._clusterCache[pre]; 538 | } 539 | if (!this._clusterCache[zoom]) { 540 | this._clusterCache[zoom] = this._computeZoomGrid(zoom); 541 | } 542 | } 543 | 544 | _computeZoomGrid(zoom) { 545 | if (!this._markerExtent) { 546 | return null; 547 | } 548 | const map = this.getMap(), 549 | r = map._getResolution(zoom) * this.layer.options['maxClusterRadius'], 550 | preT = map._getResolution(zoom - 1) ? map._getResolution(zoom - 1) * this.layer.options['maxClusterRadius'] : null; 551 | let preCache = this._clusterCache[zoom - 1]; 552 | if (!preCache && zoom - 1 >= map.getMinZoom()) { 553 | this._clusterCache[zoom - 1] = preCache = this._computeZoomGrid(zoom - 1); 554 | } 555 | // 1. format extent of markers to grids with raidus of r 556 | // 2. find point's grid in the grids 557 | // 3. sum up the point into the grid's collection 558 | const points = this._markerPoints; 559 | const sumProperty = this.layer.options['textSumProperty']; 560 | const grids = {}, 561 | min = this._markerExtent.getMin(); 562 | let gx, gy, key, 563 | pgx, pgy, pkey; 564 | for (let i = 0, len = points.length; i < len; i++) { 565 | const geo = points[i].geometry; 566 | let sumProp = 0; 567 | 568 | if (sumProperty && geo.getProperties() && geo.getProperties()[sumProperty]) { 569 | sumProp = geo.getProperties()[sumProperty]; 570 | } 571 | 572 | gx = Math.floor((points[i].x - min.x) / r); 573 | gy = Math.floor((points[i].y - min.y) / r); 574 | key = gx + '_' + gy; 575 | if (!grids[key]) { 576 | grids[key] = { 577 | 'sum' : new maptalks.Coordinate(points[i].x, points[i].y), 578 | 'center' : new maptalks.Coordinate(points[i].x, points[i].y), 579 | 'count' : 1, 580 | 'textSumProperty' : sumProp, 581 | 'children' :[geo], 582 | 'key' : key + '' 583 | }; 584 | if (preT && preCache) { 585 | pgx = Math.floor((points[i].x - min.x) / preT); 586 | pgy = Math.floor((points[i].y - min.y) / preT); 587 | pkey = pgx + '_' + pgy; 588 | grids[key]['parent'] = preCache['clusterMap'][pkey]; 589 | } 590 | } else { 591 | 592 | grids[key]['sum']._add(new maptalks.Coordinate(points[i].x, points[i].y)); 593 | grids[key]['count']++; 594 | grids[key]['center'] = grids[key]['sum'].multi(1 / grids[key]['count']); 595 | grids[key]['children'].push(geo); 596 | grids[key]['textSumProperty'] += sumProp; 597 | } 598 | } 599 | return this._mergeClusters(grids, r / 2); 600 | } 601 | 602 | _mergeClusters(grids, r) { 603 | const clusterMap = {}; 604 | for (const p in grids) { 605 | clusterMap[p] = grids[p]; 606 | } 607 | 608 | // merge adjacent clusters 609 | const merging = {}; 610 | 611 | const visited = {}; 612 | // find clusters need to merge 613 | let c1, c2; 614 | for (const p in grids) { 615 | c1 = grids[p]; 616 | if (visited[c1.key]) { 617 | continue; 618 | } 619 | const gxgy = c1.key.split('_'); 620 | const gx = +(gxgy[0]), 621 | gy = +(gxgy[1]); 622 | //traverse adjacent grids 623 | for (let ii = -1; ii <= 1; ii++) { 624 | for (let iii = -1; iii <= 1; iii++) { 625 | if (ii === 0 && iii === 0) { 626 | continue; 627 | } 628 | const key2 = (gx + ii) + '_' + (gy + iii); 629 | c2 = grids[key2]; 630 | if (c2 && this._distanceTo(c1['center'], c2['center']) <= r) { 631 | if (!merging[c1.key]) { 632 | merging[c1.key] = []; 633 | } 634 | merging[c1.key].push(c2); 635 | visited[c2.key] = 1; 636 | } 637 | } 638 | } 639 | } 640 | 641 | //merge clusters 642 | for (const m in merging) { 643 | const grid = grids[m]; 644 | if (!grid) { 645 | continue; 646 | } 647 | const toMerge = merging[m]; 648 | for (let i = 0; i < toMerge.length; i++) { 649 | if (grids[toMerge[i].key]) { 650 | grid['sum']._add(toMerge[i].sum); 651 | grid['count'] += toMerge[i].count; 652 | grid['textSumProperty'] += toMerge[i].textSumProperty; 653 | grid['children'] = grid['children'].concat(toMerge[i].children); 654 | clusterMap[toMerge[i].key] = grid; 655 | delete grids[toMerge[i].key]; 656 | } 657 | } 658 | grid['center'] = grid['sum'].multi(1 / grid['count']); 659 | } 660 | 661 | return { 662 | 'clusters' : grids, 663 | 'clusterMap' : clusterMap 664 | }; 665 | } 666 | 667 | _distanceTo(c1, c2) { 668 | const x = c1.x - c2.x, 669 | y = c1.y - c2.y; 670 | return Math.sqrt(x * x + y * y); 671 | } 672 | 673 | _stopAnim() { 674 | if (this._player && this._player.playState !== 'finished') { 675 | this._player.finish(); 676 | } 677 | } 678 | 679 | onZoomStart(param) { 680 | this._stopAnim(); 681 | super.onZoomStart(param); 682 | } 683 | 684 | onZoomEnd(param) { 685 | if (this.layer.isEmpty() || !this.layer.isVisible()) { 686 | super.onZoomEnd.apply(this, arguments); 687 | return; 688 | } 689 | this._inout = param['from'] > param['to'] ? 'in' : 'out'; 690 | this._triggerAnimate = true; 691 | this._computeGrid(); 692 | super.onZoomEnd.apply(this, arguments); 693 | } 694 | 695 | _clearDataCache() { 696 | this._stopAnim(); 697 | delete this._markerExtent; 698 | delete this._markerPoints; 699 | delete this._clusterCache; 700 | delete this._zoomInClusters; 701 | } 702 | }; 703 | return renderable; 704 | } 705 | 706 | class ClusterLayerRenderer extends ClusterLayerRenderable(maptalks.renderer.VectorLayerCanvasRenderer) { 707 | 708 | constructor(...args) { 709 | super(...args); 710 | this.init(); 711 | } 712 | } 713 | 714 | ClusterLayer.registerRenderer('canvas', ClusterLayerRenderer); 715 | 716 | if (typeof PointLayerRenderer !== 'undefined') { 717 | class ClusterGLRenderer extends ClusterLayerRenderable(PointLayerRenderer) { 718 | constructor(...args) { 719 | super(...args); 720 | this.init(); 721 | } 722 | 723 | drawOnInteracting(event, timestamp, parentContext) { 724 | if (this._currentClusters) { 725 | this.drawClusters(this._currentClusters, 1); 726 | } 727 | this.drawMarkers(); 728 | PointLayerRenderer.prototype.draw.call(this, timestamp, parentContext); 729 | } 730 | 731 | drawClusters(...args) { 732 | this._clearToDraw(); 733 | super.drawClusters(...args); 734 | this.flush(); 735 | } 736 | 737 | drawClustersFrame(...args) { 738 | this._clearToDraw(); 739 | super.drawClustersFrame(...args); 740 | this.flush(); 741 | } 742 | 743 | _clearToDraw() { 744 | this.pointCount = 0; 745 | this.bufferIndex = 0; 746 | this.opacityIndex = 0; 747 | this.textIndex = 0; 748 | } 749 | 750 | drawCluster(pt, cluster, opacity) { 751 | this._currentGrid = cluster; 752 | const { sprite, key } = this._getSprite(); 753 | const canvas = sprite.canvas; 754 | if (!sprite.data) { 755 | sprite.data = canvas.getContext('2d', { willReadFrequently: true }).getImageData(0, 0, canvas.width, canvas.height); 756 | } 757 | if (!this.clusterSprites[key]) { 758 | this.clusterSprites[key] = sprite; 759 | this.textureDirty = true; 760 | } 761 | const pos = pt.add(sprite.offset)._sub(canvas.width / 2, canvas.height / 2); 762 | let x = pos.x; 763 | let y = pos.y; 764 | const map = this.getMap(); 765 | const pixelRatio = map.getDevicePixelRatio(); 766 | const height = map.height; 767 | x = x * pixelRatio; 768 | y = (height - y) * pixelRatio; 769 | const spriteW = sprite.data.width * pixelRatio; 770 | const spriteH = sprite.data.height * pixelRatio; 771 | 772 | this.addPoint(x, y, spriteW, spriteH, opacity, key); 773 | 774 | if (this.layer.options['drawClusterText']) { 775 | maptalks.Canvas.prepareCanvasFont(fontCtx, this._textSymbol); 776 | const fontKey = fontCtx.font + '-' + fontCtx.fillStyle; 777 | const text = this._getClusterText(cluster); 778 | const { sprite, key } = this._getTextSprite(text, fontKey); 779 | if (!this.clusterTextSprites[key]) { 780 | this.clusterTextSprites[key] = sprite; 781 | this.textTextureDirty = true; 782 | } 783 | this.addTextPoint(x + spriteW / 2, y - spriteH / 2, sprite.data.width * pixelRatio, sprite.data.height * pixelRatio, key); 784 | } 785 | this.pointCount++; 786 | 787 | } 788 | 789 | _getTextSprite(text, fontKey) { 790 | if (!this._textSpriteCache) { 791 | this._textSpriteCache = {}; 792 | } 793 | const key = fontKey + '-' + text; 794 | if (!this._textSpriteCache[key]) { 795 | const dpr = this.getMap().getDevicePixelRatio(); 796 | const metrics = fontCtx.measureText(text); 797 | const textWidth = metrics.width; 798 | const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; 799 | const canvas = document.createElement('canvas'); 800 | canvas.width = textWidth * dpr; 801 | canvas.height = textHeight * dpr; 802 | const ctx = canvas.getContext('2d', { willReadFrequently: true }); 803 | ctx.scale(dpr, dpr); 804 | maptalks.Canvas.prepareCanvasFont(ctx, this._textSymbol); 805 | ctx.textBaseline = 'top'; 806 | ctx.fillText(text, 0, 0); 807 | const debugCanvas = document.getElementById('debug-text-sprite'); 808 | if (debugCanvas) { 809 | debugCanvas.width = canvas.width; 810 | debugCanvas.height = canvas.height; 811 | const ctx = debugCanvas.getContext('2d'); 812 | ctx.drawImage(canvas, 0, 0); 813 | } 814 | 815 | this._textSpriteCache[key] = { 816 | canvas, 817 | offset: ZERO_POINT, 818 | data: ctx.getImageData(0, 0, canvas.width, canvas.height) 819 | }; 820 | } 821 | return { 822 | sprite: this._textSpriteCache[key], 823 | key 824 | }; 825 | } 826 | 827 | checkMarksToDraw() { 828 | super.checkMarksToDraw(); 829 | this.drawMarkers(); 830 | } 831 | 832 | drawMarkers() { 833 | if (this._markersToDraw.dirty) { 834 | this.rebuildGeometries(); 835 | this._markersToDraw.dirty = false; 836 | } 837 | } 838 | 839 | flush(parentContext) { 840 | if (this.pointCount === 0) { 841 | return; 842 | } 843 | this._updateMesh(); 844 | const fbo = parentContext && parentContext.renderTarget && context.renderTarget.fbo; 845 | this._clusterGeometry.setDrawCount(this.pointCount * 6); 846 | const { width, height } = this.canvas; 847 | const layerOpacity = this._getLayerOpacity(); 848 | const uniforms = { 849 | resolution: [width, height], 850 | layerOpacity, 851 | dxDy: [0, 0] 852 | }; 853 | this._renderer.render(this._spriteShader, uniforms, this._scene, fbo); 854 | 855 | if (this.layer.options['drawClusterText']) { 856 | this._textGeometry.setDrawCount(this.pointCount * 6); 857 | const dx = this._textSymbol['textDx'] || 0; 858 | const dy = this._textSymbol['textDy'] || 0; 859 | uniforms.dxDy = [dx, dy]; 860 | this._renderer.render(this._spriteShader, uniforms, this._textScene, fbo); 861 | } 862 | } 863 | 864 | _updateMesh() { 865 | 866 | const isAtlasDirty = this.textureDirty; 867 | const atlas = this._genAtlas(); 868 | this._updateTexCoord(atlas, isAtlasDirty); 869 | // text 870 | if (this.layer.options['drawClusterText']) { 871 | const isAtlasDirty = this.textTextureDirty; 872 | const textAtlas = this._genTextAtlas(); 873 | this._updateTextTexCoord(textAtlas, isAtlasDirty); 874 | } 875 | 876 | this._updateGeometryData(); 877 | } 878 | 879 | addPoint(x, y, width, height, opacity, key) { 880 | this._check(); 881 | const w = width; 882 | const h = height; 883 | 884 | this.addVertex(x, y - h, opacity); 885 | this.addVertex(x + w, y - h, opacity); 886 | this.addVertex(x, y, opacity); 887 | this.addVertex(x, y, opacity); 888 | this.addVertex(x + w, y - h, opacity); 889 | this.addVertex(x + w, y, opacity); 890 | if (this.sprites[this.pointCount] !== key) { 891 | this.sprites[this.pointCount] = key; 892 | this.sprites.dirty = true; 893 | } 894 | } 895 | 896 | addVertex(x, y, opacity) { 897 | const positionBufferData = this.positionBufferData; 898 | if (positionBufferData[this.bufferIndex] !== x) { 899 | positionBufferData[this.bufferIndex] = x; 900 | positionBufferData.dirty = true; 901 | } 902 | this.bufferIndex++; 903 | if (positionBufferData[this.bufferIndex] !== y) { 904 | positionBufferData[this.bufferIndex] = y; 905 | positionBufferData.dirty = true; 906 | } 907 | this.bufferIndex++; 908 | 909 | const opacityBufferData = this.opacityBufferData; 910 | // opacity *= 255; 911 | // U8[0] = opacity; 912 | if (opacityBufferData[this.opacityIndex] !== opacity) { 913 | opacityBufferData[this.opacityIndex] = opacity; 914 | opacityBufferData.dirty = true; 915 | } 916 | this.opacityIndex++; 917 | } 918 | 919 | addTextPoint(x, y, width, height, key) { 920 | this._check(); 921 | const dpr = this.getMap().getDevicePixelRatio(); 922 | width /= dpr; 923 | height /= dpr; 924 | const w = width / 2; 925 | const h = height / 2; 926 | 927 | this.addTextVertex(x - w, y - h); 928 | this.addTextVertex(x + w, y - h); 929 | this.addTextVertex(x - w, y + h); 930 | this.addTextVertex(x - w, y + h); 931 | this.addTextVertex(x + w, y - h); 932 | this.addTextVertex(x + w, y + h); 933 | 934 | if (this.textSprites[this.pointCount] !== key) { 935 | this.textSprites[this.pointCount] = key; 936 | this.textSprites.dirty = true; 937 | } 938 | } 939 | 940 | addTextVertex(x, y) { 941 | const textPositionData = this.textPositionData; 942 | if (textPositionData[this.textIndex] !== x) { 943 | textPositionData[this.textIndex] = x; 944 | textPositionData.dirty = true; 945 | } 946 | this.textIndex++; 947 | if (textPositionData[this.textIndex] !== y) { 948 | textPositionData[this.textIndex] = y; 949 | textPositionData.dirty = true; 950 | } 951 | this.textIndex++; 952 | } 953 | 954 | _check() { 955 | if (this.pointCount >= this.maxPointCount - 1) { 956 | this.maxPointCount += 1024; 957 | const { positionBufferData, texCoordBufferData, opacityBufferData, textPositionData, textTexCoordData } = this._initBuffers(); 958 | for (let i = 0; i < this.bufferIndex; i++) { 959 | positionBufferData[i] = this.positionBufferData[i]; 960 | texCoordBufferData[i] = this.texCoordBufferData[i]; 961 | textPositionData[i] = this.textPositionData[i]; 962 | textTexCoordData[i] = this.textTexCoordData[i]; 963 | } 964 | for (let i = 0; i < this.opacityIndex; i++) { 965 | opacityBufferData[i] = this.opacityBufferData[i]; 966 | } 967 | this.positionBufferData = positionBufferData; 968 | this.texCoordBufferData = texCoordBufferData; 969 | this.opacityBufferData = opacityBufferData; 970 | this.textPositionData = textPositionData; 971 | this.textTexCoordData = textTexCoordData; 972 | } 973 | } 974 | 975 | _updateGeometryData() { 976 | // icon 977 | if (this.positionBufferData.dirty) { 978 | this._clusterGeometry.updateData('aPosition', this.positionBufferData); 979 | // console.log(this.positionBufferData); 980 | this.positionBufferData.dirty = false; 981 | } 982 | if (this.opacityBufferData.dirty) { 983 | this._clusterGeometry.updateData('aOpacity', this.opacityBufferData); 984 | this._textGeometry.updateData('aOpacity', this.opacityBufferData); 985 | this.opacityBufferData.dirty = false; 986 | } 987 | if (this.texCoordBufferData.dirty) { 988 | this._clusterGeometry.updateData('aTexCoord', this.texCoordBufferData); 989 | this.texCoordBufferData.dirty = false; 990 | } 991 | 992 | // text 993 | if (this.textPositionData.dirty) { 994 | this._textGeometry.updateData('aPosition', this.textPositionData); 995 | this.textPositionData.dirty = false; 996 | } 997 | if (this.textTexCoordData.dirty) { 998 | this._textGeometry.updateData('aTexCoord', this.textTexCoordData); 999 | this.textTexCoordData.dirty = false; 1000 | } 1001 | } 1002 | 1003 | _updateTexCoord(atlas, isAtlasDirty) { 1004 | if (!this.sprites.dirty && !isAtlasDirty) { 1005 | return; 1006 | } 1007 | const { positions, image } = atlas; 1008 | const { width, height } = image; 1009 | this.texCoordIndex = 0; 1010 | for (let i = 0; i < this.pointCount; i++) { 1011 | const bin = positions[this.sprites[i]]; 1012 | const { tl, br } = bin; 1013 | this._fillTexCoord(tl, br, width, height); 1014 | } 1015 | this.sprites.dirty = false; 1016 | } 1017 | 1018 | _updateTextTexCoord(atlas, isAtlasDirty) { 1019 | if (!this.textSprites.dirty && !isAtlasDirty) { 1020 | return; 1021 | } 1022 | const { positions, image } = atlas; 1023 | const { width, height } = image; 1024 | this.textTexCoordIndex = 0; 1025 | for (let i = 0; i < this.pointCount; i++) { 1026 | const bin = positions[this.textSprites[i]]; 1027 | const { tl, br } = bin; 1028 | this._fillTextTexCoord(tl, br, width, height); 1029 | } 1030 | this.textSprites.dirty = false; 1031 | } 1032 | 1033 | _initTexture(data, width, height) { 1034 | const config = { 1035 | data, 1036 | width, 1037 | height, 1038 | mag: 'linear', 1039 | min: 'linear', 1040 | premultiplyAlpha: true 1041 | }; 1042 | if (this._clusterTexture) { 1043 | if (this._clusterTexture.update) { 1044 | this._clusterTexture.update(config); 1045 | } else { 1046 | this._clusterTexture(config); 1047 | } 1048 | } else { 1049 | this._clusterTexture = this.device.texture(config); 1050 | } 1051 | this._clusterMesh.setUniform('sourceTexture', this._clusterTexture); 1052 | } 1053 | 1054 | _initTextTexture(data, width, height) { 1055 | const config = { 1056 | data, 1057 | width, 1058 | height, 1059 | mag: 'linear', 1060 | min: 'linear', 1061 | premultiplyAlpha: true 1062 | }; 1063 | if (this._textTexture) { 1064 | if (this._textTexture.update) { 1065 | this._textTexture.update(config); 1066 | } else { 1067 | this._textTexture(config); 1068 | } 1069 | } else { 1070 | this._textTexture = this.device.texture(config); 1071 | } 1072 | this._textMesh.setUniform('sourceTexture', this._textTexture); 1073 | } 1074 | 1075 | _fillTexCoord(tl, br, texWidth, texHeight) { 1076 | const u1 = tl[0] / texWidth; 1077 | const v1 = br[1] / texHeight; 1078 | const u2 = br[0] / texWidth; 1079 | const v2 = tl[1] / texHeight; 1080 | 1081 | this.addVertexTexCoord(u1, v1); 1082 | this.addVertexTexCoord(u2, v1); 1083 | this.addVertexTexCoord(u1, v2); 1084 | this.addVertexTexCoord(u1, v2); 1085 | this.addVertexTexCoord(u2, v1); 1086 | this.addVertexTexCoord(u2, v2); 1087 | } 1088 | 1089 | _fillTextTexCoord(tl, br, texWidth, texHeight) { 1090 | const u1 = tl[0] / texWidth; 1091 | const v1 = br[1] / texHeight; 1092 | const u2 = br[0] / texWidth; 1093 | const v2 = tl[1] / texHeight; 1094 | 1095 | this.addTextTexCoord(u1, v1); 1096 | this.addTextTexCoord(u2, v1); 1097 | this.addTextTexCoord(u1, v2); 1098 | this.addTextTexCoord(u1, v2); 1099 | this.addTextTexCoord(u2, v1); 1100 | this.addTextTexCoord(u2, v2); 1101 | } 1102 | 1103 | _genAtlas() { 1104 | if (!this.textureDirty) { 1105 | return this.atlas; 1106 | } 1107 | const { IconAtlas, RGBAImage } = getVectorPacker(); 1108 | const icons = this.clusterSprites; 1109 | const iconMap = {}; 1110 | for (const url in icons) { 1111 | const icon = icons[url]; 1112 | const { width, height, data } = icon.data; 1113 | const image = new RGBAImage({ width, height }, data); 1114 | iconMap[url] = { data: image, pixelRatio: 1 }; 1115 | } 1116 | const isWebGL1 = this.gl && (this.gl instanceof WebGLRenderingContext); 1117 | this.atlas = new IconAtlas(iconMap, { nonPowerOfTwo: !isWebGL1 }); 1118 | this.textureDirty = false; 1119 | const { image } = this.atlas; 1120 | const { width, height } = image; 1121 | this._initTexture(image.data, width, height); 1122 | return this.atlas; 1123 | } 1124 | 1125 | _genTextAtlas() { 1126 | if (!this.textTextureDirty) { 1127 | return this.textAtlas; 1128 | } 1129 | const { IconAtlas, RGBAImage } = getVectorPacker(); 1130 | const texts = this.clusterTextSprites; 1131 | const textMap = {}; 1132 | for (const key in texts) { 1133 | const textSprite = texts[key]; 1134 | const { width, height, data } = textSprite.data; 1135 | const image = new RGBAImage({ width, height }, data); 1136 | textMap[key] = { data: image, pixelRatio: 1 }; 1137 | } 1138 | const isWebGL1 = this.gl && (this.gl instanceof WebGLRenderingContext); 1139 | this.textAtlas = new IconAtlas(textMap, { nonPowerOfTwo: !isWebGL1 }); 1140 | const { image } = this.textAtlas; 1141 | const { width, height } = image; 1142 | this._initTextTexture(image.data, width, height); 1143 | this.textTextureDirty = false; 1144 | 1145 | const debugCanvas = document.getElementById('debug-text-atlas'); 1146 | if (debugCanvas) { 1147 | debugCanvas.width = width; 1148 | debugCanvas.height = height; 1149 | const ctx = debugCanvas.getContext('2d'); 1150 | ctx.putImageData(new ImageData(new Uint8ClampedArray(image.data.buffer), width, height), 0, 0); 1151 | } 1152 | 1153 | return this.textAtlas; 1154 | } 1155 | 1156 | addVertexTexCoord(u, v) { 1157 | const texCoordBufferData = this.texCoordBufferData; 1158 | if (texCoordBufferData[this.texCoordIndex] !== u) { 1159 | texCoordBufferData[this.texCoordIndex] = u; 1160 | texCoordBufferData.dirty = true; 1161 | } 1162 | this.texCoordIndex++; 1163 | if (texCoordBufferData[this.texCoordIndex] !== v) { 1164 | texCoordBufferData[this.texCoordIndex] = v; 1165 | texCoordBufferData.dirty = true; 1166 | } 1167 | this.texCoordIndex++; 1168 | } 1169 | 1170 | addTextTexCoord(u, v) { 1171 | const textTexCoordData = this.textTexCoordData; 1172 | if (textTexCoordData[this.textTexCoordIndex] !== u) { 1173 | textTexCoordData[this.textTexCoordIndex] = u; 1174 | textTexCoordData.dirty = true; 1175 | } 1176 | this.textTexCoordIndex++; 1177 | if (textTexCoordData[this.textTexCoordIndex] !== v) { 1178 | textTexCoordData[this.textTexCoordIndex] = v; 1179 | textTexCoordData.dirty = true; 1180 | } 1181 | this.textTexCoordIndex++; 1182 | } 1183 | 1184 | initContext() { 1185 | // this. 1186 | this._initClusterShader(); 1187 | this._initClusterMeshes(); 1188 | return super.initContext(); 1189 | } 1190 | 1191 | onRemove() { 1192 | if (this._spriteShader) { 1193 | this._spriteShader.dispose(); 1194 | delete this._spriteShader; 1195 | } 1196 | if (this._clusterMesh) { 1197 | this._clusterMesh.dispose(); 1198 | delete this._clusterMesh; 1199 | } 1200 | if (this._clusterGeometry) { 1201 | this._clusterGeometry.dispose(); 1202 | delete this._clusterGeometry; 1203 | } 1204 | if (this._textMesh) { 1205 | this._textMesh.dispose(); 1206 | delete this._textMesh; 1207 | } 1208 | if (this._textGeometry) { 1209 | this._textGeometry.dispose(); 1210 | delete this._textGeometry; 1211 | } 1212 | if (this._clusterTexture) { 1213 | this._clusterTexture.destroy(); 1214 | delete this._clusterTexture; 1215 | } 1216 | if (this._textTexture) { 1217 | this._textTexture.destroy(); 1218 | delete this._textTexture; 1219 | } 1220 | return super.onRemove(); 1221 | } 1222 | 1223 | _initClusterShader() { 1224 | const viewport = { 1225 | x : 0, 1226 | y : 0, 1227 | width : () => { 1228 | return this.canvas ? this.canvas.width : 1; 1229 | }, 1230 | height : () => { 1231 | return this.canvas ? this.canvas.height : 1; 1232 | } 1233 | }; 1234 | 1235 | const extraCommandProps = { 1236 | viewport, 1237 | depth: { 1238 | enable: false 1239 | }, 1240 | blend: { 1241 | enable: true, 1242 | func: { 1243 | src: 1, 1244 | dst: 'one minus src alpha' 1245 | } 1246 | } 1247 | }; 1248 | 1249 | this._spriteShader = new reshader.MeshShader({ 1250 | name: 'cluster-sprite', 1251 | vert, 1252 | frag, 1253 | wgslVert, 1254 | wgslFrag, 1255 | extraCommandProps 1256 | }); 1257 | } 1258 | 1259 | _initClusterMeshes() { 1260 | this.maxPointCount = 1024; 1261 | this.pointCount = 0; 1262 | this.clusterSprites = {}; 1263 | this.clusterTextSprites = {}; 1264 | this.sprites = []; 1265 | this.textSprites = []; 1266 | this.spriteCluster = []; 1267 | 1268 | const { 1269 | positionBufferData, texCoordBufferData, opacityBufferData, 1270 | textPositionData, textTexCoordData 1271 | } = this._initBuffers(); 1272 | this.positionBufferData = positionBufferData; 1273 | this.texCoordBufferData = texCoordBufferData; 1274 | this.opacityBufferData = opacityBufferData; 1275 | this.textPositionData = textPositionData; 1276 | this.textTexCoordData = textTexCoordData; 1277 | 1278 | this._clusterGeometry = new reshader.Geometry({ 1279 | aPosition: this.positionBufferData, 1280 | aTexCoord: this.texCoordBufferData, 1281 | aOpacity: this.opacityBufferData 1282 | }, null, 0, { 1283 | positionSize: 2 1284 | }); 1285 | this._clusterGeometry.generateBuffers(this.device); 1286 | this._clusterMesh = new reshader.Mesh(this._clusterGeometry); 1287 | this._scene = new reshader.Scene([this._clusterMesh]); 1288 | 1289 | this._textGeometry = new reshader.Geometry({ 1290 | aPosition: this.textPositionData, 1291 | aTexCoord: this.textTexCoordData, 1292 | aOpacity: this.opacityBufferData 1293 | }, null, 0, { 1294 | positionSize: 2 1295 | }); 1296 | this._textGeometry.generateBuffers(this.device); 1297 | this._textMesh = new reshader.Mesh(this._textGeometry); 1298 | this._textScene = new reshader.Scene([this._textMesh]); 1299 | 1300 | this._renderer = new reshader.Renderer(this.device); 1301 | } 1302 | 1303 | _initBuffers() { 1304 | const vertexSize = 2; 1305 | const texCoordSize = 2; 1306 | const opacitySize = 1; 1307 | 1308 | const positionBufferData = new Float32Array(this.maxPointCount * vertexSize * 6); 1309 | const texCoordBufferData = new Float32Array(this.maxPointCount * texCoordSize * 6); 1310 | const opacityBufferData = new Float32Array(this.maxPointCount * opacitySize * 6); 1311 | // opacityBufferData.fill(255); 1312 | 1313 | const textPositionData = new Float32Array(this.maxPointCount * vertexSize * 6); 1314 | const textTexCoordData = new Float32Array(this.maxPointCount * texCoordSize * 6); 1315 | 1316 | return { positionBufferData, texCoordBufferData, opacityBufferData, textPositionData, textTexCoordData }; 1317 | } 1318 | 1319 | _getLayerOpacity() { 1320 | let layerOpacity = this.layer && this.layer.options['opacity']; 1321 | if (maptalks.Util.isNil(layerOpacity)) { 1322 | layerOpacity = 1; 1323 | } 1324 | return layerOpacity; 1325 | } 1326 | } 1327 | ClusterLayer.registerRenderer('gl', ClusterGLRenderer); 1328 | ClusterLayer.registerRenderer('gpu', ClusterGLRenderer); 1329 | } 1330 | 1331 | function getSymbolStamp(symbol) { 1332 | const values = []; 1333 | for (const p in symbol) { 1334 | if (p[0] === '_') { 1335 | continue; 1336 | } 1337 | values.push(symbol[p]); 1338 | } 1339 | return values.join('|'); 1340 | } 1341 | -------------------------------------------------------------------------------- /dist/maptalks.markercluster.es.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"maptalks.markercluster.es.js","sources":["../index.js"],"sourcesContent":["import * as maptalks from 'maptalks';\nimport { reshader } from '@maptalks/gl';\nimport { getVectorPacker, PointLayerRenderer } from '@maptalks/vt';\nimport vert from './glsl/sprite.vert';\nimport frag from './glsl/sprite.frag';\nimport wgslVert from './wgsl/sprite_vert.wgsl';\nimport wgslFrag from './wgsl/sprite_frag.wgsl';\n\nconst FONT_CANVAS = document.createElement('canvas');\nconst fontCtx = FONT_CANVAS.getContext('2d');\nconst ZERO_POINT = new maptalks.Point(0, 0);\n\nconst MarkerLayerClazz = maptalks.DrawToolLayer.markerLayerClazz;\nlet renderer = 'canvas';\nconst RendererClazz = MarkerLayerClazz.getRendererClass('canvas');\nif (!RendererClazz) {\n renderer = 'gl';\n}\n\nconst options = {\n 'renderer': renderer,\n 'maxClusterRadius' : 160,\n 'textSumProperty' : null,\n 'symbol' : null,\n 'drawClusterText' : true,\n 'textSymbol' : null,\n 'animation' : true,\n 'animationDuration' : 450,\n 'maxClusterZoom' : null,\n 'noClusterWithOneMarker':true,\n 'forceRenderOnZooming' : true\n};\n\nexport class ClusterLayer extends MarkerLayerClazz {\n /**\n * Reproduce a ClusterLayer from layer's profile JSON.\n * @param {Object} json - layer's profile JSON\n * @return {maptalks.ClusterLayer}\n * @static\n * @private\n * @function\n */\n static fromJSON(json) {\n if (!json || json['type'] !== 'ClusterLayer') { return null; }\n const layer = new ClusterLayer(json['id'], json['options']);\n const geoJSONs = json['geometries'];\n const geometries = [];\n for (let i = 0; i < geoJSONs.length; i++) {\n const geo = maptalks.Geometry.fromJSON(geoJSONs[i]);\n if (geo) {\n geometries.push(geo);\n }\n }\n layer.addGeometry(geometries);\n return layer;\n }\n\n addMarker(markers) {\n return this.addGeometry(markers);\n }\n\n addGeometry(markers) {\n for (let i = 0, len = markers.length; i < len; i++) {\n if (!(markers[i] instanceof maptalks.Marker)) {\n throw new Error('Only a point(Marker) can be added into a ClusterLayer');\n }\n }\n return super.addGeometry.apply(this, arguments);\n }\n\n onConfig(conf) {\n super.onConfig(conf);\n if (conf['maxClusterRadius'] ||\n conf['symbol'] ||\n conf['drawClusterText'] ||\n conf['textSymbol'] ||\n conf['maxClusterZoom']) {\n const renderer = this._getRenderer();\n if (renderer) {\n renderer.render();\n }\n }\n return this;\n }\n\n /**\n * Identify the clusters on the given coordinate\n * @param {maptalks.Coordinate} coordinate - coordinate to identify\n * @return {Object|Geometry[]} result: cluster { center : [cluster's center], children : [geometries in the cluster] } or markers\n */\n identify(coordinate, options) {\n const map = this.getMap(),\n maxZoom = this.options['maxClusterZoom'];\n if (maxZoom && map && map.getZoom() > maxZoom) {\n return super.identify(coordinate, options);\n }\n if (this._getRenderer()) {\n return this._getRenderer().identify(coordinate, options);\n }\n return null;\n }\n\n /**\n * Export the ClusterLayer's JSON.\n * @return {Object} layer's JSON\n */\n toJSON() {\n const json = super.toJSON.call(this);\n json['type'] = 'ClusterLayer';\n return json;\n }\n /**\n * Get the ClusterLayer's current clusters\n * @return {Object} layer's clusters\n **/\n getClusters() {\n const renderer = this._getRenderer();\n if (renderer) {\n return renderer._currentClusters || [];\n }\n return [];\n }\n}\n\n// merge to define ClusterLayer's default options.\nClusterLayer.mergeOptions(options);\n\n// register ClusterLayer's JSON type for JSON deserialization.\nClusterLayer.registerJSONType('ClusterLayer');\n\nconst defaultTextSymbol = {\n 'textFaceName' : '\"microsoft yahei\"',\n 'textSize' : 16,\n 'textDx' : 0,\n 'textDy' : 0\n};\n\nconst defaultSymbol = {\n 'markerType' : 'ellipse',\n 'markerFill' : { property:'count', type:'interval', stops: [[0, 'rgb(135, 196, 240)'], [9, '#1bbc9b'], [99, 'rgb(216, 115, 149)']] },\n 'markerFillOpacity' : 0.7,\n 'markerLineOpacity' : 1,\n 'markerLineWidth' : 3,\n 'markerLineColor' : '#fff',\n 'markerWidth' : { property:'count', type:'interval', stops: [[0, 40], [9, 60], [99, 80]] },\n 'markerHeight' : { property:'count', type:'interval', stops: [[0, 40], [9, 60], [99, 80]] }\n};\n\nconst ClusterLayerRenderable = function(Base) {\n const renderable = class extends Base {\n init() {\n this._refreshStyle();\n this._clusterNeedRedraw = true;\n }\n\n checkResources() {\n if (!super.checkResources) {\n return [];\n }\n const symbol = this.layer.options['symbol'] || defaultSymbol;\n const resources = super.checkResources.apply(this, arguments);\n if (symbol !== this._symbolResourceChecked) {\n const res = maptalks.Util.getExternalResources(symbol, true);\n if (res) {\n resources.push.apply(resources, res);\n }\n this._symbolResourceChecked = symbol;\n }\n return resources;\n }\n\n draw() {\n if (!this.canvas) {\n this.prepareCanvas();\n }\n const map = this.getMap();\n const zoom = map.getZoom();\n const maxClusterZoom = this.layer.options['maxClusterZoom'];\n if (maxClusterZoom && zoom > maxClusterZoom) {\n delete this._currentClusters;\n this.checkMarksToDraw();\n super.draw.apply(this, arguments);\n return;\n }\n if (this._clusterNeedRedraw) {\n this._clearDataCache();\n this._computeGrid();\n this._clusterNeedRedraw = false;\n }\n let clusters;\n if (this._triggerAnimate) {\n this._startAnimation(zoom);\n }\n if (this._animateDelta) {\n clusters = this._animateClusters;\n } else {\n const zoomClusters = this._clusterCache[zoom] ? this._clusterCache[zoom]['clusters'] : null;\n clusters = this.getClustersToDraw(zoomClusters);\n clusters.zoom = zoom;\n }\n this._drawLayer(clusters);\n }\n\n _startAnimation(zoom) {\n const zoomClusters = this._clusterCache[zoom] ? this._clusterCache[zoom]['clusters'] : null;\n const clusters = this.getClustersToDraw(zoomClusters);\n clusters.zoom = zoom;\n\n this._animateClusters = clusters;\n this._parentClusters = this._currentClusters || clusters;\n const layer = this.layer;\n if (layer.options['animation'] && this._triggerAnimate) {\n let dr = [0, 1];\n if (this._inout === 'in') {\n dr = [1, 0];\n }\n this._animateDelta = dr[0];\n this._player = maptalks.animation.Animation.animate(\n { 'd' : dr },\n { 'speed' : layer.options['animationDuration'], 'easing' : 'inAndOut' },\n frame => {\n this._animateDelta = frame.styles.d;\n if (frame.state.playState === 'finished') {\n delete this._animateDelta;\n delete this._inout;\n delete this._animateClusters;\n delete this._parentClusters\n }\n this.setToRedraw();\n }\n )\n .play();\n this.setToRedraw();\n }\n this._triggerAnimate = false;\n }\n\n checkMarksToDraw() {\n const dirty = this._markersToDraw !== this.layer._geoList;\n this._markersToDraw = this.layer._geoList;\n this._markersToDraw.dirty = dirty;\n }\n\n getClustersToDraw(zoomClusters) {\n const oldMarkersToDraw = this._markersToDraw || [];\n this._markersToDraw = [];\n const map = this.getMap();\n const font = maptalks.StringUtil.getFont(this._textSymbol),\n digitLen = maptalks.StringUtil.stringLength('9', font).toPoint();\n const extent = map.getContainerExtent(),\n clusters = [];\n let pt, pExt, sprite, width, height, markerIndex = 0, isMarkerDirty = false;\n for (const p in zoomClusters) {\n this._currentGrid = zoomClusters[p];\n if (zoomClusters[p]['count'] === 1 && this.layer.options['noClusterWithOneMarker']) {\n const marker = zoomClusters[p]['children'][0];\n marker._cluster = zoomClusters[p];\n if (!isMarkerDirty && oldMarkersToDraw[markerIndex++] !== marker) {\n isMarkerDirty = true;\n }\n this._markersToDraw.push(marker);\n continue;\n }\n sprite = this._getSprite().sprite;\n width = sprite.canvas.width;\n height = sprite.canvas.height;\n pt = map._prjToContainerPoint(zoomClusters[p]['center']);\n pExt = new maptalks.PointExtent(pt.sub(width, height), pt.add(width, height));\n if (!extent.intersects(pExt)) {\n continue;\n }\n if (!zoomClusters[p]['textSize']) {\n const text = this._getClusterText(zoomClusters[p]);\n zoomClusters[p]['textSize'] = new maptalks.Point(digitLen.x * text.length, digitLen.y)._multi(1 / 2);\n }\n clusters.push(zoomClusters[p]);\n }\n if (oldMarkersToDraw.length !== this._markersToDraw.length) {\n isMarkerDirty = true;\n }\n this._markersToDraw.dirty = isMarkerDirty;\n return clusters;\n }\n\n drawOnInteracting(...args) {\n if (this._currentClusters) {\n this.drawClusters(this._currentClusters, 1);\n }\n super.drawOnInteracting(...args);\n }\n\n getCurrentNeedRenderGeos() {\n if (this._markersToDraw) {\n return this._markersToDraw;\n }\n return [];\n }\n\n _getCurrentNeedRenderGeos() {\n return this.getCurrentNeedRenderGeos();\n }\n\n forEachGeo(fn, context) {\n if (this._markersToDraw) {\n this._markersToDraw.forEach((g) => {\n if (context) {\n fn.call(context, g);\n } else {\n fn(g);\n }\n });\n }\n }\n\n onGeometryShow() {\n this._clusterNeedRedraw = true;\n super.onGeometryShow.apply(this, arguments);\n }\n\n onGeometryHide() {\n this._clusterNeedRedraw = true;\n super.onGeometryHide.apply(this, arguments);\n }\n\n onGeometryAdd() {\n this._clusterNeedRedraw = true;\n super.onGeometryAdd.apply(this, arguments);\n }\n\n onGeometryRemove() {\n this._clusterNeedRedraw = true;\n super.onGeometryRemove.apply(this, arguments);\n }\n\n onGeometryPositionChange() {\n this._clusterNeedRedraw = true;\n super.onGeometryPositionChange.apply(this, arguments);\n }\n\n onRemove() {\n this._clearDataCache();\n }\n\n identify(coordinate, options) {\n const map = this.getMap(),\n maxZoom = this.layer.options['maxClusterZoom'];\n if (maxZoom && map.getZoom() > maxZoom) {\n return super.identify(coordinate, options);\n }\n if (this._currentClusters) {\n const point = map.coordinateToContainerPoint(coordinate);\n const old = this._currentGrid;\n for (let i = 0; i < this._currentClusters.length; i++) {\n const c = this._currentClusters[i];\n const pt = map._prjToContainerPoint(c['center']);\n this._currentGrid = c;\n const markerWidth = this._getSprite().sprite.canvas.width;\n\n if (point.distanceTo(pt) <= markerWidth) {\n return {\n 'center' : map.getProjection().unproject(c.center.copy()),\n 'children' : c.children.slice(0)\n };\n }\n }\n this._currentGrid = old;\n }\n\n // if no clusters is hit, identify markers\n if (this._markersToDraw && this._markersToDraw[0]) {\n const point = map.coordinateToContainerPoint(coordinate);\n return this.layer._hitGeos(this._markersToDraw, point, options);\n }\n return null;\n }\n\n onSymbolChanged() {\n this._refreshStyle();\n this._computeGrid();\n this._stopAnim();\n this.setToRedraw();\n }\n\n _refreshStyle() {\n const symbol = this.layer.options['symbol'] || defaultSymbol;\n const textSymbol = this.layer.options['textSymbol'] || defaultTextSymbol;\n const argFn = () => [this.getMap().getZoom(), this._currentGrid];\n this._symbol = maptalks.MapboxUtil.loadFunctionTypes(symbol, argFn);\n this._textSymbol = maptalks.MapboxUtil.loadFunctionTypes(textSymbol, argFn);\n }\n\n _drawLayer(clusters) {\n this._currentClusters = clusters;\n if (this._animateDelta >= 0) {\n if (this._inout === 'in') {\n this.drawClustersFrame(clusters, this._parentClusters, this._animateDelta);\n } else {\n this.drawClustersFrame(this._parentClusters, clusters, this._animateDelta);\n }\n } else {\n this.drawClusters(clusters, 1);\n }\n this.drawMarkers();\n this.completeRender();\n }\n\n drawMarkers() {\n super.drawGeos();\n }\n\n drawClustersFrame(parentClusters, toClusters, ratio) {\n this.prepareCanvas();\n const map = this.getMap(),\n drawn = {};\n if (parentClusters) {\n parentClusters.forEach(c => {\n const p = map._prjToContainerPoint(c['center']);\n if (!drawn[c.key]) {\n drawn[c.key] = 1;\n this.drawCluster(p, c, 1 - ratio);\n }\n });\n }\n if (ratio === 0 || !toClusters) {\n return;\n }\n const z = parentClusters.zoom,\n r = map._getResolution(z) * this.layer.options['maxClusterRadius'],\n min = this._markerExtent.getMin();\n toClusters.forEach(c => {\n let pt = map._prjToContainerPoint(c['center']);\n const center = c.center;\n const pgx = Math.floor((center.x - min.x) / r),\n pgy = Math.floor((center.y - min.y) / r);\n const pkey = pgx + '_' + pgy;\n const parent = this._clusterCache[z] ? this._clusterCache[z]['clusterMap'][pkey] : null;\n if (parent) {\n const pp = map._prjToContainerPoint(parent['center']);\n pt = pp.add(pt.sub(pp)._multi(ratio));\n }\n this.drawCluster(pt, c, ratio > 0.5 ? 1 : ratio);\n });\n }\n\n drawClusters(clusters, ratio) {\n if (!clusters) {\n return;\n }\n this.prepareCanvas();\n const map = this.getMap();\n clusters.forEach(c => {\n const pt = map._prjToContainerPoint(c['center']);\n this.drawCluster(pt, c, ratio > 0.5 ? 1 : ratio);\n });\n\n }\n\n drawCluster(pt, cluster, op) {\n this._currentGrid = cluster;\n const ctx = this.context;\n const sprite = this._getSprite().sprite;\n const opacity = ctx.globalAlpha;\n if (opacity * op === 0) {\n return;\n }\n ctx.globalAlpha = opacity * op;\n if (sprite) {\n const pos = pt.add(sprite.offset)._sub(sprite.canvas.width / 2, sprite.canvas.height / 2);\n maptalks.Canvas.image(ctx, sprite.canvas, pos.x, pos.y);\n }\n\n if (this.layer.options['drawClusterText'] && cluster['textSize']) {\n maptalks.Canvas.prepareCanvasFont(ctx, this._textSymbol);\n ctx.textBaseline = 'middle';\n const dx = this._textSymbol['textDx'] || 0;\n const dy = this._textSymbol['textDy'] || 0;\n const text = this._getClusterText(cluster);\n maptalks.Canvas.fillText(ctx, text, pt.sub(cluster['textSize'].x, 0)._add(dx, dy));\n }\n ctx.globalAlpha = opacity;\n }\n\n _getClusterText(cluster) {\n const text = this.layer.options['textSumProperty'] ? cluster['textSumProperty'] : cluster['count'];\n return text + '';\n }\n\n _getSprite() {\n if (!this._spriteCache) {\n this._spriteCache = {};\n }\n const key = getSymbolStamp(this._symbol);\n if (!this._spriteCache[key]) {\n this._spriteCache[key] = new maptalks.Marker([0, 0], { 'symbol' : this._symbol })._getSprite(this.resources, this.getMap().CanvasClass);\n }\n return {\n sprite: this._spriteCache[key],\n key\n };\n }\n\n _initGridSystem() {\n const points = [];\n let extent, c;\n this.layer.forEach(g => {\n if (!g.isVisible()) {\n return;\n }\n c = g._getPrjCoordinates();\n if (!extent) {\n extent = g._getPrjExtent();\n } else {\n extent = extent._combine(g._getPrjExtent());\n }\n points.push({\n x : c.x,\n y : c.y,\n id : g._getInternalId(),\n geometry : g\n });\n });\n this._markerExtent = extent;\n this._markerPoints = points;\n }\n\n _computeGrid() {\n const map = this.getMap(),\n zoom = map.getZoom();\n if (!this._markerExtent) {\n this._initGridSystem();\n }\n if (!this._clusterCache) {\n this._clusterCache = {};\n }\n const pre = map._getResolution(map.getMinZoom()) > map._getResolution(map.getMaxZoom()) ? zoom - 1 : zoom + 1;\n if (this._clusterCache[pre] && this._clusterCache[pre].length === this.layer.getCount()) {\n this._clusterCache[zoom] = this._clusterCache[pre];\n }\n if (!this._clusterCache[zoom]) {\n this._clusterCache[zoom] = this._computeZoomGrid(zoom);\n }\n }\n\n _computeZoomGrid(zoom) {\n if (!this._markerExtent) {\n return null;\n }\n const map = this.getMap(),\n r = map._getResolution(zoom) * this.layer.options['maxClusterRadius'],\n preT = map._getResolution(zoom - 1) ? map._getResolution(zoom - 1) * this.layer.options['maxClusterRadius'] : null;\n let preCache = this._clusterCache[zoom - 1];\n if (!preCache && zoom - 1 >= map.getMinZoom()) {\n this._clusterCache[zoom - 1] = preCache = this._computeZoomGrid(zoom - 1);\n }\n // 1. format extent of markers to grids with raidus of r\n // 2. find point's grid in the grids\n // 3. sum up the point into the grid's collection\n const points = this._markerPoints;\n const sumProperty = this.layer.options['textSumProperty'];\n const grids = {},\n min = this._markerExtent.getMin();\n let gx, gy, key,\n pgx, pgy, pkey;\n for (let i = 0, len = points.length; i < len; i++) {\n const geo = points[i].geometry;\n let sumProp = 0;\n\n if (sumProperty && geo.getProperties() && geo.getProperties()[sumProperty]) {\n sumProp = geo.getProperties()[sumProperty];\n }\n\n gx = Math.floor((points[i].x - min.x) / r);\n gy = Math.floor((points[i].y - min.y) / r);\n key = gx + '_' + gy;\n if (!grids[key]) {\n grids[key] = {\n 'sum' : new maptalks.Coordinate(points[i].x, points[i].y),\n 'center' : new maptalks.Coordinate(points[i].x, points[i].y),\n 'count' : 1,\n 'textSumProperty' : sumProp,\n 'children' :[geo],\n 'key' : key + ''\n };\n if (preT && preCache) {\n pgx = Math.floor((points[i].x - min.x) / preT);\n pgy = Math.floor((points[i].y - min.y) / preT);\n pkey = pgx + '_' + pgy;\n grids[key]['parent'] = preCache['clusterMap'][pkey];\n }\n } else {\n\n grids[key]['sum']._add(new maptalks.Coordinate(points[i].x, points[i].y));\n grids[key]['count']++;\n grids[key]['center'] = grids[key]['sum'].multi(1 / grids[key]['count']);\n grids[key]['children'].push(geo);\n grids[key]['textSumProperty'] += sumProp;\n }\n }\n return this._mergeClusters(grids, r / 2);\n }\n\n _mergeClusters(grids, r) {\n const clusterMap = {};\n for (const p in grids) {\n clusterMap[p] = grids[p];\n }\n\n // merge adjacent clusters\n const merging = {};\n\n const visited = {};\n // find clusters need to merge\n let c1, c2;\n for (const p in grids) {\n c1 = grids[p];\n if (visited[c1.key]) {\n continue;\n }\n const gxgy = c1.key.split('_');\n const gx = +(gxgy[0]),\n gy = +(gxgy[1]);\n //traverse adjacent grids\n for (let ii = -1; ii <= 1; ii++) {\n for (let iii = -1; iii <= 1; iii++) {\n if (ii === 0 && iii === 0) {\n continue;\n }\n const key2 = (gx + ii) + '_' + (gy + iii);\n c2 = grids[key2];\n if (c2 && this._distanceTo(c1['center'], c2['center']) <= r) {\n if (!merging[c1.key]) {\n merging[c1.key] = [];\n }\n merging[c1.key].push(c2);\n visited[c2.key] = 1;\n }\n }\n }\n }\n\n //merge clusters\n for (const m in merging) {\n const grid = grids[m];\n if (!grid) {\n continue;\n }\n const toMerge = merging[m];\n for (let i = 0; i < toMerge.length; i++) {\n if (grids[toMerge[i].key]) {\n grid['sum']._add(toMerge[i].sum);\n grid['count'] += toMerge[i].count;\n grid['textSumProperty'] += toMerge[i].textSumProperty;\n grid['children'] = grid['children'].concat(toMerge[i].children);\n clusterMap[toMerge[i].key] = grid;\n delete grids[toMerge[i].key];\n }\n }\n grid['center'] = grid['sum'].multi(1 / grid['count']);\n }\n\n return {\n 'clusters' : grids,\n 'clusterMap' : clusterMap\n };\n }\n\n _distanceTo(c1, c2) {\n const x = c1.x - c2.x,\n y = c1.y - c2.y;\n return Math.sqrt(x * x + y * y);\n }\n\n _stopAnim() {\n if (this._player && this._player.playState !== 'finished') {\n this._player.finish();\n }\n }\n\n onZoomStart(param) {\n this._stopAnim();\n super.onZoomStart(param);\n }\n\n onZoomEnd(param) {\n if (this.layer.isEmpty() || !this.layer.isVisible()) {\n super.onZoomEnd.apply(this, arguments);\n return;\n }\n this._inout = param['from'] > param['to'] ? 'in' : 'out';\n this._triggerAnimate = true;\n this._computeGrid();\n super.onZoomEnd.apply(this, arguments);\n }\n\n _clearDataCache() {\n this._stopAnim();\n delete this._markerExtent;\n delete this._markerPoints;\n delete this._clusterCache;\n delete this._zoomInClusters;\n }\n };\n return renderable;\n}\n\nclass ClusterLayerRenderer extends ClusterLayerRenderable(maptalks.renderer.VectorLayerCanvasRenderer) {\n\n constructor(...args) {\n super(...args);\n this.init();\n }\n}\n\nClusterLayer.registerRenderer('canvas', ClusterLayerRenderer);\n\nif (typeof PointLayerRenderer !== 'undefined') {\n class ClusterGLRenderer extends ClusterLayerRenderable(PointLayerRenderer) {\n constructor(...args) {\n super(...args);\n this.init();\n }\n\n drawOnInteracting(event, timestamp, parentContext) {\n if (this._currentClusters) {\n this.drawClusters(this._currentClusters, 1);\n }\n this.drawMarkers();\n PointLayerRenderer.prototype.draw.call(this, timestamp, parentContext);\n }\n\n drawClusters(...args) {\n this._clearToDraw();\n super.drawClusters(...args);\n this.flush();\n }\n\n drawClustersFrame(...args) {\n this._clearToDraw();\n super.drawClustersFrame(...args);\n this.flush();\n }\n\n _clearToDraw() {\n this.pointCount = 0;\n this.bufferIndex = 0;\n this.opacityIndex = 0;\n this.textIndex = 0;\n }\n\n drawCluster(pt, cluster, opacity) {\n this._currentGrid = cluster;\n const { sprite, key } = this._getSprite();\n const canvas = sprite.canvas;\n if (!sprite.data) {\n sprite.data = canvas.getContext('2d', { willReadFrequently: true }).getImageData(0, 0, canvas.width, canvas.height);\n }\n if (!this.clusterSprites[key]) {\n this.clusterSprites[key] = sprite;\n this.textureDirty = true;\n }\n const pos = pt.add(sprite.offset)._sub(canvas.width / 2, canvas.height / 2);\n let x = pos.x;\n let y = pos.y;\n const map = this.getMap();\n const pixelRatio = map.getDevicePixelRatio();\n const height = map.height;\n x = x * pixelRatio;\n y = (height - y) * pixelRatio;\n const spriteW = sprite.data.width * pixelRatio;\n const spriteH = sprite.data.height * pixelRatio;\n\n this.addPoint(x, y, spriteW, spriteH, opacity, key);\n\n if (this.layer.options['drawClusterText']) {\n maptalks.Canvas.prepareCanvasFont(fontCtx, this._textSymbol);\n const fontKey = fontCtx.font + '-' + fontCtx.fillStyle;\n const text = this._getClusterText(cluster);\n const { sprite, key } = this._getTextSprite(text, fontKey);\n if (!this.clusterTextSprites[key]) {\n this.clusterTextSprites[key] = sprite;\n this.textTextureDirty = true;\n }\n this.addTextPoint(x + spriteW / 2, y - spriteH / 2, sprite.data.width * pixelRatio, sprite.data.height * pixelRatio, key);\n }\n this.pointCount++;\n\n }\n\n _getTextSprite(text, fontKey) {\n if (!this._textSpriteCache) {\n this._textSpriteCache = {};\n }\n const key = fontKey + '-' + text;\n if (!this._textSpriteCache[key]) {\n const dpr = this.getMap().getDevicePixelRatio();\n const metrics = fontCtx.measureText(text);\n const textWidth = metrics.width;\n const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;\n const canvas = document.createElement('canvas');\n canvas.width = textWidth * dpr;\n canvas.height = textHeight * dpr;\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n ctx.scale(dpr, dpr);\n maptalks.Canvas.prepareCanvasFont(ctx, this._textSymbol);\n ctx.textBaseline = 'top';\n ctx.fillText(text, 0, 0);\n const debugCanvas = document.getElementById('debug-text-sprite');\n if (debugCanvas) {\n debugCanvas.width = canvas.width;\n debugCanvas.height = canvas.height;\n const ctx = debugCanvas.getContext('2d');\n ctx.drawImage(canvas, 0, 0);\n }\n\n this._textSpriteCache[key] = {\n canvas,\n offset: ZERO_POINT,\n data: ctx.getImageData(0, 0, canvas.width, canvas.height)\n };\n }\n return {\n sprite: this._textSpriteCache[key],\n key\n };\n }\n\n checkMarksToDraw() {\n super.checkMarksToDraw();\n this.drawMarkers();\n }\n\n drawMarkers() {\n if (this._markersToDraw.dirty) {\n this.rebuildGeometries();\n this._markersToDraw.dirty = false;\n }\n }\n\n flush(parentContext) {\n if (this.pointCount === 0) {\n return;\n }\n this._updateMesh();\n const fbo = parentContext && parentContext.renderTarget && context.renderTarget.fbo;\n this._clusterGeometry.setDrawCount(this.pointCount * 6);\n const { width, height } = this.canvas;\n const layerOpacity = this._getLayerOpacity();\n const uniforms = {\n resolution: [width, height],\n layerOpacity,\n dxDy: [0, 0]\n };\n this._renderer.render(this._spriteShader, uniforms, this._scene, fbo);\n\n if (this.layer.options['drawClusterText']) {\n this._textGeometry.setDrawCount(this.pointCount * 6);\n const dx = this._textSymbol['textDx'] || 0;\n const dy = this._textSymbol['textDy'] || 0;\n uniforms.dxDy = [dx, dy];\n this._renderer.render(this._spriteShader, uniforms, this._textScene, fbo);\n }\n }\n\n _updateMesh() {\n\n const isAtlasDirty = this.textureDirty;\n const atlas = this._genAtlas();\n this._updateTexCoord(atlas, isAtlasDirty);\n // text\n if (this.layer.options['drawClusterText']) {\n const isAtlasDirty = this.textTextureDirty;\n const textAtlas = this._genTextAtlas();\n this._updateTextTexCoord(textAtlas, isAtlasDirty);\n }\n\n this._updateGeometryData();\n }\n\n addPoint(x, y, width, height, opacity, key) {\n this._check();\n const w = width;\n const h = height;\n\n this.addVertex(x, y - h, opacity);\n this.addVertex(x + w, y - h, opacity);\n this.addVertex(x, y, opacity);\n this.addVertex(x, y, opacity);\n this.addVertex(x + w, y - h, opacity);\n this.addVertex(x + w, y, opacity);\n if (this.sprites[this.pointCount] !== key) {\n this.sprites[this.pointCount] = key;\n this.sprites.dirty = true;\n }\n }\n\n addVertex(x, y, opacity) {\n const positionBufferData = this.positionBufferData;\n if (positionBufferData[this.bufferIndex] !== x) {\n positionBufferData[this.bufferIndex] = x;\n positionBufferData.dirty = true;\n }\n this.bufferIndex++;\n if (positionBufferData[this.bufferIndex] !== y) {\n positionBufferData[this.bufferIndex] = y;\n positionBufferData.dirty = true;\n }\n this.bufferIndex++;\n\n const opacityBufferData = this.opacityBufferData;\n // opacity *= 255;\n // U8[0] = opacity;\n if (opacityBufferData[this.opacityIndex] !== opacity) {\n opacityBufferData[this.opacityIndex] = opacity;\n opacityBufferData.dirty = true;\n }\n this.opacityIndex++;\n }\n\n addTextPoint(x, y, width, height, key) {\n this._check();\n const dpr = this.getMap().getDevicePixelRatio();\n width /= dpr;\n height /= dpr;\n const w = width / 2;\n const h = height / 2;\n\n this.addTextVertex(x - w, y - h);\n this.addTextVertex(x + w, y - h);\n this.addTextVertex(x - w, y + h);\n this.addTextVertex(x - w, y + h);\n this.addTextVertex(x + w, y - h);\n this.addTextVertex(x + w, y + h);\n\n if (this.textSprites[this.pointCount] !== key) {\n this.textSprites[this.pointCount] = key;\n this.textSprites.dirty = true;\n }\n }\n\n addTextVertex(x, y) {\n const textPositionData = this.textPositionData;\n if (textPositionData[this.textIndex] !== x) {\n textPositionData[this.textIndex] = x;\n textPositionData.dirty = true;\n }\n this.textIndex++;\n if (textPositionData[this.textIndex] !== y) {\n textPositionData[this.textIndex] = y;\n textPositionData.dirty = true;\n }\n this.textIndex++;\n }\n\n _check() {\n if (this.pointCount >= this.maxPointCount - 1) {\n this.maxPointCount += 1024;\n const { positionBufferData, texCoordBufferData, opacityBufferData, textPositionData, textTexCoordData } = this._initBuffers();\n for (let i = 0; i < this.bufferIndex; i++) {\n positionBufferData[i] = this.positionBufferData[i];\n texCoordBufferData[i] = this.texCoordBufferData[i];\n textPositionData[i] = this.textPositionData[i];\n textTexCoordData[i] = this.textTexCoordData[i];\n }\n for (let i = 0; i < this.opacityIndex; i++) {\n opacityBufferData[i] = this.opacityBufferData[i];\n }\n this.positionBufferData = positionBufferData;\n this.texCoordBufferData = texCoordBufferData;\n this.opacityBufferData = opacityBufferData;\n this.textPositionData = textPositionData;\n this.textTexCoordData = textTexCoordData;\n }\n }\n\n _updateGeometryData() {\n // icon\n if (this.positionBufferData.dirty) {\n this._clusterGeometry.updateData('aPosition', this.positionBufferData);\n // console.log(this.positionBufferData);\n this.positionBufferData.dirty = false;\n }\n if (this.opacityBufferData.dirty) {\n this._clusterGeometry.updateData('aOpacity', this.opacityBufferData);\n this._textGeometry.updateData('aOpacity', this.opacityBufferData);\n this.opacityBufferData.dirty = false;\n }\n if (this.texCoordBufferData.dirty) {\n this._clusterGeometry.updateData('aTexCoord', this.texCoordBufferData);\n this.texCoordBufferData.dirty = false;\n }\n\n // text\n if (this.textPositionData.dirty) {\n this._textGeometry.updateData('aPosition', this.textPositionData);\n this.textPositionData.dirty = false;\n }\n if (this.textTexCoordData.dirty) {\n this._textGeometry.updateData('aTexCoord', this.textTexCoordData);\n this.textTexCoordData.dirty = false;\n }\n }\n\n _updateTexCoord(atlas, isAtlasDirty) {\n if (!this.sprites.dirty && !isAtlasDirty) {\n return;\n }\n const { positions, image } = atlas;\n const { width, height } = image;\n this.texCoordIndex = 0;\n for (let i = 0; i < this.pointCount; i++) {\n const bin = positions[this.sprites[i]];\n const { tl, br } = bin;\n this._fillTexCoord(tl, br, width, height);\n }\n this.sprites.dirty = false;\n }\n\n _updateTextTexCoord(atlas, isAtlasDirty) {\n if (!this.textSprites.dirty && !isAtlasDirty) {\n return;\n }\n const { positions, image } = atlas;\n const { width, height } = image;\n this.textTexCoordIndex = 0;\n for (let i = 0; i < this.pointCount; i++) {\n const bin = positions[this.textSprites[i]];\n const { tl, br } = bin;\n this._fillTextTexCoord(tl, br, width, height);\n }\n this.textSprites.dirty = false;\n }\n\n _initTexture(data, width, height) {\n const config = {\n data,\n width,\n height,\n mag: 'linear',\n min: 'linear',\n premultiplyAlpha: true\n };\n if (this._clusterTexture) {\n if (this._clusterTexture.update) {\n this._clusterTexture.update(config);\n } else {\n this._clusterTexture(config);\n }\n } else {\n this._clusterTexture = this.device.texture(config);\n }\n this._clusterMesh.setUniform('sourceTexture', this._clusterTexture);\n }\n\n _initTextTexture(data, width, height) {\n const config = {\n data,\n width,\n height,\n mag: 'linear',\n min: 'linear',\n premultiplyAlpha: true\n };\n if (this._textTexture) {\n if (this._textTexture.update) {\n this._textTexture.update(config);\n } else {\n this._textTexture(config);\n }\n } else {\n this._textTexture = this.device.texture(config);\n }\n this._textMesh.setUniform('sourceTexture', this._textTexture);\n }\n\n _fillTexCoord(tl, br, texWidth, texHeight) {\n const u1 = tl[0] / texWidth;\n const v1 = br[1] / texHeight;\n const u2 = br[0] / texWidth;\n const v2 = tl[1] / texHeight;\n\n this.addVertexTexCoord(u1, v1);\n this.addVertexTexCoord(u2, v1);\n this.addVertexTexCoord(u1, v2);\n this.addVertexTexCoord(u1, v2);\n this.addVertexTexCoord(u2, v1);\n this.addVertexTexCoord(u2, v2);\n }\n\n _fillTextTexCoord(tl, br, texWidth, texHeight) {\n const u1 = tl[0] / texWidth;\n const v1 = br[1] / texHeight;\n const u2 = br[0] / texWidth;\n const v2 = tl[1] / texHeight;\n\n this.addTextTexCoord(u1, v1);\n this.addTextTexCoord(u2, v1);\n this.addTextTexCoord(u1, v2);\n this.addTextTexCoord(u1, v2);\n this.addTextTexCoord(u2, v1);\n this.addTextTexCoord(u2, v2);\n }\n\n _genAtlas() {\n if (!this.textureDirty) {\n return this.atlas;\n }\n const { IconAtlas, RGBAImage } = getVectorPacker();\n const icons = this.clusterSprites;\n const iconMap = {};\n for (const url in icons) {\n const icon = icons[url];\n const { width, height, data } = icon.data;\n const image = new RGBAImage({ width, height }, data);\n iconMap[url] = { data: image, pixelRatio: 1 };\n }\n const isWebGL1 = this.gl && (this.gl instanceof WebGLRenderingContext);\n this.atlas = new IconAtlas(iconMap, { nonPowerOfTwo: !isWebGL1 });\n this.textureDirty = false;\n const { image } = this.atlas;\n const { width, height } = image;\n this._initTexture(image.data, width, height);\n return this.atlas;\n }\n\n _genTextAtlas() {\n if (!this.textTextureDirty) {\n return this.textAtlas;\n }\n const { IconAtlas, RGBAImage } = getVectorPacker();\n const texts = this.clusterTextSprites;\n const textMap = {};\n for (const key in texts) {\n const textSprite = texts[key];\n const { width, height, data } = textSprite.data;\n const image = new RGBAImage({ width, height }, data);\n textMap[key] = { data: image, pixelRatio: 1 };\n }\n const isWebGL1 = this.gl && (this.gl instanceof WebGLRenderingContext);\n this.textAtlas = new IconAtlas(textMap, { nonPowerOfTwo: !isWebGL1 });\n const { image } = this.textAtlas;\n const { width, height } = image;\n this._initTextTexture(image.data, width, height);\n this.textTextureDirty = false;\n\n const debugCanvas = document.getElementById('debug-text-atlas');\n if (debugCanvas) {\n debugCanvas.width = width;\n debugCanvas.height = height;\n const ctx = debugCanvas.getContext('2d');\n ctx.putImageData(new ImageData(new Uint8ClampedArray(image.data.buffer), width, height), 0, 0);\n }\n\n return this.textAtlas;\n }\n\n addVertexTexCoord(u, v) {\n const texCoordBufferData = this.texCoordBufferData;\n if (texCoordBufferData[this.texCoordIndex] !== u) {\n texCoordBufferData[this.texCoordIndex] = u;\n texCoordBufferData.dirty = true;\n }\n this.texCoordIndex++;\n if (texCoordBufferData[this.texCoordIndex] !== v) {\n texCoordBufferData[this.texCoordIndex] = v;\n texCoordBufferData.dirty = true;\n }\n this.texCoordIndex++;\n }\n\n addTextTexCoord(u, v) {\n const textTexCoordData = this.textTexCoordData;\n if (textTexCoordData[this.textTexCoordIndex] !== u) {\n textTexCoordData[this.textTexCoordIndex] = u;\n textTexCoordData.dirty = true;\n }\n this.textTexCoordIndex++;\n if (textTexCoordData[this.textTexCoordIndex] !== v) {\n textTexCoordData[this.textTexCoordIndex] = v;\n textTexCoordData.dirty = true;\n }\n this.textTexCoordIndex++;\n }\n\n initContext() {\n // this.\n this._initClusterShader();\n this._initClusterMeshes();\n return super.initContext();\n }\n\n onRemove() {\n if (this._spriteShader) {\n this._spriteShader.dispose();\n delete this._spriteShader;\n }\n if (this._clusterMesh) {\n this._clusterMesh.dispose();\n delete this._clusterMesh;\n }\n if (this._clusterGeometry) {\n this._clusterGeometry.dispose();\n delete this._clusterGeometry;\n }\n if (this._textMesh) {\n this._textMesh.dispose();\n delete this._textMesh;\n }\n if (this._textGeometry) {\n this._textGeometry.dispose();\n delete this._textGeometry;\n }\n if (this._clusterTexture) {\n this._clusterTexture.destroy();\n delete this._clusterTexture;\n }\n if (this._textTexture) {\n this._textTexture.destroy();\n delete this._textTexture;\n }\n return super.onRemove();\n }\n\n _initClusterShader() {\n const viewport = {\n x : 0,\n y : 0,\n width : () => {\n return this.canvas ? this.canvas.width : 1;\n },\n height : () => {\n return this.canvas ? this.canvas.height : 1;\n }\n };\n\n const extraCommandProps = {\n viewport,\n depth: {\n enable: false\n },\n blend: {\n enable: true,\n func: {\n src: 1,\n dst: 'one minus src alpha'\n }\n }\n };\n\n this._spriteShader = new reshader.MeshShader({\n name: 'cluster-sprite',\n vert,\n frag,\n wgslVert,\n wgslFrag,\n extraCommandProps\n });\n }\n\n _initClusterMeshes() {\n this.maxPointCount = 1024;\n this.pointCount = 0;\n this.clusterSprites = {};\n this.clusterTextSprites = {};\n this.sprites = [];\n this.textSprites = [];\n this.spriteCluster = [];\n\n const {\n positionBufferData, texCoordBufferData, opacityBufferData,\n textPositionData, textTexCoordData\n } = this._initBuffers();\n this.positionBufferData = positionBufferData;\n this.texCoordBufferData = texCoordBufferData;\n this.opacityBufferData = opacityBufferData;\n this.textPositionData = textPositionData;\n this.textTexCoordData = textTexCoordData;\n\n this._clusterGeometry = new reshader.Geometry({\n aPosition: this.positionBufferData,\n aTexCoord: this.texCoordBufferData,\n aOpacity: this.opacityBufferData\n }, null, 0, {\n positionSize: 2\n });\n this._clusterGeometry.generateBuffers(this.device);\n this._clusterMesh = new reshader.Mesh(this._clusterGeometry);\n this._scene = new reshader.Scene([this._clusterMesh]);\n\n this._textGeometry = new reshader.Geometry({\n aPosition: this.textPositionData,\n aTexCoord: this.textTexCoordData,\n aOpacity: this.opacityBufferData\n }, null, 0, {\n positionSize: 2\n });\n this._textGeometry.generateBuffers(this.device);\n this._textMesh = new reshader.Mesh(this._textGeometry);\n this._textScene = new reshader.Scene([this._textMesh]);\n\n this._renderer = new reshader.Renderer(this.device);\n }\n\n _initBuffers() {\n const vertexSize = 2;\n const texCoordSize = 2;\n const opacitySize = 1;\n\n const positionBufferData = new Float32Array(this.maxPointCount * vertexSize * 6);\n const texCoordBufferData = new Float32Array(this.maxPointCount * texCoordSize * 6);\n const opacityBufferData = new Float32Array(this.maxPointCount * opacitySize * 6);\n // opacityBufferData.fill(255);\n\n const textPositionData = new Float32Array(this.maxPointCount * vertexSize * 6);\n const textTexCoordData = new Float32Array(this.maxPointCount * texCoordSize * 6);\n\n return { positionBufferData, texCoordBufferData, opacityBufferData, textPositionData, textTexCoordData };\n }\n\n _getLayerOpacity() {\n let layerOpacity = this.layer && this.layer.options['opacity'];\n if (maptalks.Util.isNil(layerOpacity)) {\n layerOpacity = 1;\n }\n return layerOpacity;\n }\n }\n ClusterLayer.registerRenderer('gl', ClusterGLRenderer);\n ClusterLayer.registerRenderer('gpu', ClusterGLRenderer);\n}\n\nfunction getSymbolStamp(symbol) {\n const values = [];\n for (const p in symbol) {\n if (p[0] === '_') {\n continue;\n }\n values.push(symbol[p]);\n }\n return values.join('|');\n}\n"],"names":["fontCtx","document","createElement","getContext","ZERO_POINT","maptalks","Point","MarkerLayerClazz","DrawToolLayer","markerLayerClazz","renderer","getRendererClass","options","maxClusterRadius","textSumProperty","symbol","drawClusterText","textSymbol","animation","animationDuration","maxClusterZoom","noClusterWithOneMarker","forceRenderOnZooming","ClusterLayer","fromJSON","json","layer","geoJSONs","geometries","i","length","geo","Geometry","push","addGeometry","addMarker","markers","this","len","Marker","Error","super","apply","arguments","onConfig","conf","_getRenderer","render","identify","coordinate","map","getMap","maxZoom","getZoom","toJSON","call","getClusters","_currentClusters","mergeOptions","registerJSONType","defaultTextSymbol","textFaceName","textSize","textDx","textDy","defaultSymbol","markerType","markerFill","property","type","stops","markerFillOpacity","markerLineOpacity","markerLineWidth","markerLineColor","markerWidth","markerHeight","ClusterLayerRenderable","Base","init","_refreshStyle","_clusterNeedRedraw","checkResources","resources","_symbolResourceChecked","res","Util","getExternalResources","draw","canvas","prepareCanvas","zoom","checkMarksToDraw","clusters","_clearDataCache","_computeGrid","_triggerAnimate","_startAnimation","_animateDelta","_animateClusters","zoomClusters","_clusterCache","getClustersToDraw","_drawLayer","_parentClusters","dr","_inout","_player","Animation","animate","d","speed","easing","frame","styles","state","playState","setToRedraw","play","dirty","_markersToDraw","_geoList","oldMarkersToDraw","font","StringUtil","getFont","_textSymbol","digitLen","stringLength","toPoint","extent","getContainerExtent","pt","pExt","sprite","width","height","markerIndex","isMarkerDirty","p","_currentGrid","marker","_cluster","_getSprite","_prjToContainerPoint","PointExtent","sub","add","intersects","text","_getClusterText","x","y","_multi","drawOnInteracting","args","drawClusters","getCurrentNeedRenderGeos","_getCurrentNeedRenderGeos","forEachGeo","fn","context","forEach","g","onGeometryShow","onGeometryHide","onGeometryAdd","onGeometryRemove","onGeometryPositionChange","onRemove","point","coordinateToContainerPoint","old","c","distanceTo","center","getProjection","unproject","copy","children","slice","_hitGeos","onSymbolChanged","_stopAnim","argFn","_symbol","MapboxUtil","loadFunctionTypes","drawClustersFrame","drawMarkers","completeRender","drawGeos","parentClusters","toClusters","ratio","drawn","key","drawCluster","z","r","_getResolution","min","_markerExtent","getMin","pkey","Math","floor","parent","pp","cluster","op","ctx","opacity","globalAlpha","pos","offset","_sub","Canvas","image","prepareCanvasFont","textBaseline","dx","dy","fillText","_add","_spriteCache","values","join","getSymbolStamp","CanvasClass","_initGridSystem","points","isVisible","_getPrjCoordinates","_combine","_getPrjExtent","id","_getInternalId","geometry","_markerPoints","pre","getMinZoom","getMaxZoom","getCount","_computeZoomGrid","preT","preCache","sumProperty","grids","gx","gy","pgx","pgy","sumProp","getProperties","Coordinate","multi","sum","count","_mergeClusters","clusterMap","merging","visited","c1","c2","gxgy","split","ii","iii","_distanceTo","m","grid","toMerge","concat","sqrt","finish","onZoomStart","param","onZoomEnd","isEmpty","_zoomInClusters","ClusterLayerRenderer","VectorLayerCanvasRenderer","constructor","registerRenderer","PointLayerRenderer","ClusterGLRenderer","event","timestamp","parentContext","prototype","_clearToDraw","flush","pointCount","bufferIndex","opacityIndex","textIndex","data","willReadFrequently","getImageData","clusterSprites","textureDirty","pixelRatio","getDevicePixelRatio","spriteW","spriteH","addPoint","fontKey","fillStyle","_getTextSprite","clusterTextSprites","textTextureDirty","addTextPoint","_textSpriteCache","dpr","metrics","measureText","textWidth","textHeight","actualBoundingBoxAscent","actualBoundingBoxDescent","scale","debugCanvas","getElementById","drawImage","rebuildGeometries","_updateMesh","fbo","renderTarget","_clusterGeometry","setDrawCount","uniforms","resolution","layerOpacity","_getLayerOpacity","dxDy","_renderer","_spriteShader","_scene","_textGeometry","_textScene","isAtlasDirty","atlas","_genAtlas","_updateTexCoord","textAtlas","_genTextAtlas","_updateTextTexCoord","_updateGeometryData","_check","w","h","addVertex","sprites","positionBufferData","opacityBufferData","addTextVertex","textSprites","textPositionData","maxPointCount","texCoordBufferData","textTexCoordData","_initBuffers","updateData","positions","texCoordIndex","bin","tl","br","_fillTexCoord","textTexCoordIndex","_fillTextTexCoord","_initTexture","config","mag","premultiplyAlpha","_clusterTexture","update","device","texture","_clusterMesh","setUniform","_initTextTexture","_textTexture","_textMesh","texWidth","texHeight","u1","v1","u2","v2","addVertexTexCoord","addTextTexCoord","IconAtlas","RGBAImage","getVectorPacker","icons","iconMap","url","icon","isWebGL1","gl","WebGLRenderingContext","nonPowerOfTwo","texts","textMap","textSprite","putImageData","ImageData","Uint8ClampedArray","buffer","u","v","initContext","_initClusterShader","_initClusterMeshes","dispose","destroy","extraCommandProps","viewport","depth","enable","blend","func","src","dst","reshader","MeshShader","name","vert","frag","wgslVert","wgslFrag","spriteCluster","aPosition","aTexCoord","aOpacity","positionSize","generateBuffers","Mesh","Scene","Renderer","Float32Array","isNil"],"mappings":"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"} --------------------------------------------------------------------------------