├── LICENSE
├── README.md
├── examples
└── clustering
│ └── clusteringExample.html
└── src
└── clustering
├── Cluster.js
├── ClusterAlgorithm.js
├── ClusterItem.js
├── ClusterRenderer.js
├── DefaultCluster.js
├── DefaultClusterItem.js
├── DefaultClusterManager.js
├── DefaultClusterRenderer.js
├── build
├── clusterer.cat.js
├── clusterer.min.js
├── clusterer_exports.js
├── google_maps_api_v3_14.js
└── rebuild.sh
├── kdqueue
├── ClusterEdge.js
├── ClusterObject.js
├── HierarchicalClusterer.js
└── KdTreeQueueItem.js
└── lib
├── MinPriorityQueue.js
├── PriorityQueueNode.js
├── kdnode.js
└── kdtree.js
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "[]" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright [yyyy] [name of copyright owner]
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **Please note:** This repository is not currently maintained, and is kept for historical purpose only.
2 |
3 | 
4 |
--------------------------------------------------------------------------------
/examples/clustering/clusteringExample.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Simple Clustering Example
7 |
15 |
16 |
17 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/clustering/Cluster.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * A group of ClusterItems.
19 | * @interface
20 | */
21 | function Cluster() {}
22 |
23 | /**
24 | * @return {!google.maps.LatLng}
25 | */
26 | Cluster.prototype.getPosition = function() {};
27 |
28 | /**
29 | * Returns the ClusterItems belonging to this group.
30 | * @return {!Array.}
31 | */
32 | Cluster.prototype.getItems = function() {};
33 |
34 | /**
35 | * Returns the number of ClusterItems belonging to this group.
36 | * @return {number}
37 | */
38 | Cluster.prototype.getSize = function() {};
39 |
--------------------------------------------------------------------------------
/src/clustering/ClusterAlgorithm.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Logic for computing clusters.
19 | * @interface
20 | */
21 | function ClusterAlgorithm() {}
22 |
23 | /**
24 | * Add a ClusterItem to be clustered.
25 | * @param {ClusterItem} item
26 | */
27 | ClusterAlgorithm.prototype.addItem = function(item) {};
28 |
29 | /**
30 | * Add an array of ClusterItems to be clustered.
31 | * @param {!Array.} items
32 | */
33 | ClusterAlgorithm.prototype.addItems = function(items) {};
34 |
35 | /**
36 | * Remove all items from items to be clustered.
37 | */
38 | ClusterAlgorithm.prototype.clearItems = function() {};
39 |
40 | /**
41 | * Returns an array of the items currently handled by the clustering algorithm.
42 | * @return {!Array.}
43 | */
44 | ClusterAlgorithm.prototype.getItems = function() {};
45 |
46 | /**
47 | * Remove a ClusterItem from the items to be clustered, if present.
48 | * @param {ClusterItem} item
49 | */
50 | ClusterAlgorithm.prototype.removeItem = function(item) {};
51 |
52 | /**
53 | * Returns the set of Clusters to display at specified zoom level.
54 | * @param {number} zoom
55 | * @return {!Array.}
56 | */
57 | ClusterAlgorithm.prototype.getClusters = function(zoom) {};
58 |
--------------------------------------------------------------------------------
/src/clustering/ClusterItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Represents a single data point.
19 | * @interface
20 | */
21 | function ClusterItem() {}
22 |
23 | /**
24 | * The position of this item. This must always return the same value.
25 | * @return {!google.maps.LatLng}
26 | */
27 | ClusterItem.prototype.getPosition = function() {};
28 |
--------------------------------------------------------------------------------
/src/clustering/ClusterRenderer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Rendering for clusters and markers.
19 | * @interface
20 | */
21 | function ClusterRenderer() {}
22 |
23 | /**
24 | * Displays the set of specified clusters.
25 | * @param {!Array.} clusters
26 | */
27 | ClusterRenderer.prototype.onClustersChanged = function(clusters) {};
28 |
--------------------------------------------------------------------------------
/src/clustering/DefaultCluster.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * A simple cluster class representing a number of ClusterItems clustered at a
19 | * single position.
20 | * @param {!google.maps.LatLng} position
21 | * @param {!Array.} items
22 | * @constructor
23 | * @implements {Cluster}
24 | */
25 | function DefaultCluster(position, items) {
26 | // TODO(bckenny): add support for {lat: num, lng: num}
27 | /**
28 | * Position of cluster.
29 | * @private {!google.maps.LatLng}
30 | */
31 | this.position_ = position;
32 |
33 | /**
34 | * Items in cluster.
35 | * @private {!Array.}
36 | */
37 | this.items_ = items;
38 |
39 | /**
40 | * Number of items in cluster.
41 | * @private {number}
42 | */
43 | this.size_ = this.items_.length;
44 | }
45 |
46 | /**
47 | * @inheritDoc
48 | */
49 | DefaultCluster.prototype.getPosition = function() {
50 | return this.position_;
51 | };
52 |
53 | /**
54 | * @inheritDoc
55 | */
56 | DefaultCluster.prototype.getItems = function() {
57 | return this.items_;
58 | };
59 |
60 | /**
61 | * @inheritDoc
62 | */
63 | DefaultCluster.prototype.getSize = function() {
64 | return this.size_;
65 | };
66 |
--------------------------------------------------------------------------------
/src/clustering/DefaultClusterItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * A single data point to be clustered.
19 | * @param {!google.maps.LatLng} latLng
20 | * @param {!google.maps.MarkerOptions} options
21 | * @implements {ClusterItem}
22 | * @constructor
23 | */
24 | function DefaultClusterItem(latLng, options) {
25 | // TODO(bckenny): add support for {lat: num, lng: num}
26 | /**
27 | * Position of item.
28 | * @private {!google.maps.LatLng}
29 | */
30 | this.position_ = latLng;
31 |
32 | // TODO(bckenny): move to general options, not markeroptions
33 | /**
34 | * Options associated with item.
35 | * @private {!google.maps.MarkerOptions}
36 | */
37 | this.options_ = options;
38 | }
39 |
40 | /**
41 | * @inheritDoc
42 | */
43 | DefaultClusterItem.prototype.getPosition = function() {
44 | return this.position_;
45 | };
46 |
47 | /**
48 | * Get options associated with this point.
49 | */
50 | DefaultClusterItem.prototype.getMarkerOptions = function() {
51 | return this.options_;
52 | };
53 |
--------------------------------------------------------------------------------
/src/clustering/DefaultClusterManager.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2013 Google Inc. All Rights Reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /* global HierarchicalClusterer, DefaultClusterRenderer */
19 |
20 | /**
21 | * A simple cluster manager implementation. By default, uses a hierarchical
22 | * clusterer algorithm and the DefaultClusterRenderer for rendering.
23 | * @param {!google.maps.Map} map
24 | * @param {ClusterRenderer=} opt_renderer
25 | * @param {ClusterAlgorithm=} opt_algorithm
26 | * @constructor
27 | */
28 | function DefaultClusterManager(map, opt_renderer, opt_algorithm) {
29 | /**
30 | * @private {!google.maps.Map}
31 | */
32 | this.map_ = map;
33 |
34 | var algo = opt_algorithm ? opt_algorithm : new HierarchicalClusterer();
35 | /**
36 | * @private {!ClusterAlgorithm}
37 | */
38 | this.algorithm_ = algo;
39 |
40 | var renderer = opt_renderer ? opt_renderer : new DefaultClusterRenderer(map);
41 | /**
42 | * @private {!ClusterRenderer}
43 | */
44 | this.renderer_ = renderer;
45 | }
46 |
47 | /**
48 | * Set the renderer used to display clusters on the map.
49 | * @param {!ClusterRenderer} clusterRenderer
50 | */
51 | DefaultClusterManager.prototype.setRenderer = function(clusterRenderer) {
52 | this.renderer_ = clusterRenderer;
53 | };
54 |
55 | /**
56 | * Set the algorith used to cluster items.
57 | * @param {!ClusterAlgorithm} algorithm
58 | */
59 | DefaultClusterManager.prototype.setAlgorithm = function(algorithm) {
60 | var items = this.algorithm_.getItems();
61 | this.algorithm_ = algorithm;
62 | this.algorithm_.addItems(items);
63 | };
64 |
65 | /**
66 | * Add a ClusterItem to be clustered.
67 | * @param {!ClusterItem} item
68 | */
69 | DefaultClusterManager.prototype.addItem = function(item) {
70 | this.algorithm_.addItem(item);
71 | };
72 |
73 | /**
74 | * Add an array of ClusterItems to be clustered.
75 | * @param {!Array.} items
76 | */
77 | DefaultClusterManager.prototype.addItems = function(items) {
78 | this.algorithm_.addItems(items);
79 | };
80 |
81 | /**
82 | * Remove all items from items to be clustered.
83 | */
84 | DefaultClusterManager.prototype.clearItems = function() {
85 | this.algorithm_.clearItems();
86 | };
87 |
88 | /**
89 | * Remove a ClusterItem from the items to be clustered, if present.
90 | * @param {ClusterItem} item
91 | */
92 | DefaultClusterManager.prototype.removeItem = function(item) {
93 | this.algorithm_.removeItem(item);
94 | };
95 |
96 | /**
97 | * Forces a re-clustering and updates the view.
98 | */
99 | DefaultClusterManager.prototype.cluster = function() {
100 | // TODO(bckenny): currently the only need for map is zoom
101 | var zoom = this.map_.getZoom();
102 | var clusters = this.algorithm_.getClusters(zoom);
103 | this.renderer_.onClustersChanged(clusters);
104 | };
105 |
106 | DefaultClusterManager.prototype.onCameraChange = function(cameraPosition) {
107 | // TODO(bckenny)
108 | };
109 |
--------------------------------------------------------------------------------
/src/clustering/DefaultClusterRenderer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * A very simple cluster renderer. When clusters change, existing ones are
19 | * repurposed, new ones are created as needed, and unneeded ones are removed
20 | * from map.
21 | * @param {!google.maps.Map} map
22 | * @constructor
23 | * @implements {ClusterRenderer}
24 | */
25 | function DefaultClusterRenderer(map) {
26 | /**
27 | * The map for this renderer.
28 | * @private {!google.maps.Map}
29 | */
30 | this.map_ = map;
31 |
32 | /**
33 | * A pool of markers used for display and expanded on demand.
34 | * @private {!Array.}
35 | */
36 | this.markers_ = [];
37 | }
38 |
39 | /**
40 | * A color scale hack.
41 | * @private {number}
42 | * @const
43 | */
44 | DefaultClusterRenderer.SIZE_COLOR_SCALE_ = 30;
45 |
46 | /**
47 | * @inheritDoc
48 | */
49 | DefaultClusterRenderer.prototype.onClustersChanged = function(clusters) {
50 | // reuse markers from existing pool, expanding pool as needed
51 | for (var i = 0; i < clusters.length; i++) {
52 | var marker;
53 | if (i < this.markers_.length) {
54 | marker = this.markers_[i];
55 | if (!marker.getMap()) {
56 | marker.setMap(this.map_);
57 | }
58 | } else {
59 | marker = new google.maps.Marker({map: this.map_});
60 | this.markers_[i] = marker;
61 | }
62 |
63 | marker.setPosition(clusters[i].getPosition());
64 |
65 | var colorScale = clusters[i].getSize() /
66 | DefaultClusterRenderer.SIZE_COLOR_SCALE_;
67 | colorScale = Math.min(1, Math.sqrt(colorScale));
68 | var red = Math.min(Math.round(255 * colorScale), 255);
69 | marker.setIcon(/** @type {google.maps.Symbol} */ ({
70 | path: google.maps.SymbolPath.CIRCLE,
71 | scale: 10,
72 | strokeWeight: 4,
73 | fillColor: 'rgb(' + red + ',0,0)',
74 | fillOpacity: 1
75 | }));
76 | }
77 |
78 | // remove any member of the marker pool that was unused
79 | for (; i < this.markers_.length; i++) {
80 | if (this.markers_[i].getMap()) {
81 | this.markers_[i].setMap(null);
82 | }
83 | }
84 | };
85 |
--------------------------------------------------------------------------------
/src/clustering/build/clusterer.cat.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2013 Google Inc. All Rights Reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /* global HierarchicalClusterer, DefaultClusterRenderer */
19 |
20 | /**
21 | * A simple cluster manager implementation. By default, uses a hierarchical
22 | * clusterer algorithm and the DefaultClusterRenderer for rendering.
23 | * @param {!google.maps.Map} map
24 | * @constructor
25 | */
26 | function DefaultClusterManager(map) {
27 | /**
28 | * @private {!google.maps.Map}
29 | */
30 | this.map_ = map;
31 |
32 | /**
33 | * @private {!ClusterAlgorithm}
34 | */
35 | this.algorithm_ = new HierarchicalClusterer();
36 |
37 | /**
38 | * @private {!ClusterRenderer}
39 | */
40 | this.renderer_ = new DefaultClusterRenderer(map);
41 | }
42 |
43 | /**
44 | * Set the renderer used to display clusters on the map.
45 | * @param {!ClusterRenderer} clusterRenderer
46 | */
47 | DefaultClusterManager.prototype.setRenderer = function(clusterRenderer) {
48 | this.renderer_ = clusterRenderer;
49 | };
50 |
51 | /**
52 | * Set the algorith used to cluster items.
53 | * @param {!ClusterAlgorithm} algorithm
54 | */
55 | DefaultClusterManager.prototype.setAlgorithm = function(algorithm) {
56 | var items = this.algorithm_.getItems();
57 | this.algorithm_ = algorithm;
58 | this.algorithm_.addItems(items);
59 | };
60 |
61 | /**
62 | * Add a ClusterItem to be clustered.
63 | * @param {!ClusterItem} item
64 | */
65 | DefaultClusterManager.prototype.addItem = function(item) {
66 | this.algorithm_.addItem(item);
67 | };
68 |
69 | /**
70 | * Add an array of ClusterItems to be clustered.
71 | * @param {!Array.} items
72 | */
73 | DefaultClusterManager.prototype.addItems = function(items) {
74 | this.algorithm_.addItems(items);
75 | };
76 |
77 | /**
78 | * Remove all items from items to be clustered.
79 | */
80 | DefaultClusterManager.prototype.clearItems = function() {
81 | this.algorithm_.clearItems();
82 | };
83 |
84 | /**
85 | * Remove a ClusterItem from the items to be clustered, if present.
86 | * @param {ClusterItem} item
87 | */
88 | DefaultClusterManager.prototype.removeItem = function(item) {
89 | this.algorithm_.removeItem(item);
90 | };
91 |
92 | /**
93 | * Forces a re-clustering and updates the view.
94 | */
95 | DefaultClusterManager.prototype.cluster = function() {
96 | // TODO(bckenny): currently the only need for map is zoom
97 | var zoom = this.map_.getZoom();
98 | var clusters = this.algorithm_.getClusters(zoom);
99 | this.renderer_.onClustersChanged(clusters);
100 | };
101 |
102 | DefaultClusterManager.prototype.onCameraChange = function(cameraPosition) {
103 | // TODO(bckenny)
104 | };
105 | /**
106 | * Copyright 2013 Google Inc. All Rights Reserved.
107 | *
108 | * Licensed under the Apache License, Version 2.0 (the "License");
109 | * you may not use this file except in compliance with the License.
110 | * You may obtain a copy of the License at
111 | *
112 | * http://www.apache.org/licenses/LICENSE-2.0
113 | *
114 | * Unless required by applicable law or agreed to in writing, software
115 | * distributed under the License is distributed on an "AS IS" BASIS,
116 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
117 | * See the License for the specific language governing permissions and
118 | * limitations under the License.
119 | */
120 |
121 | /**
122 | * A single data point to be clustered.
123 | * @param {!google.maps.LatLng} latLng
124 | * @param {!google.maps.MarkerOptions} options
125 | * @implements {ClusterItem}
126 | * @constructor
127 | */
128 | function DefaultClusterItem(latLng, options) {
129 | // TODO(bckenny): add support for {lat: num, lng: num}
130 | /**
131 | * Position of item.
132 | * @private {!google.maps.LatLng}
133 | */
134 | this.position_ = latLng;
135 |
136 | // TODO(bckenny): move to general options, not markeroptions
137 | /**
138 | * Options associated with item.
139 | * @private {!google.maps.MarkerOptions}
140 | */
141 | this.options_ = options;
142 | }
143 |
144 | /**
145 | * @inheritDoc
146 | */
147 | DefaultClusterItem.prototype.getPosition = function() {
148 | return this.position_;
149 | };
150 |
151 | /**
152 | * Get options associated with this point.
153 | */
154 | DefaultClusterItem.prototype.getMarkerOptions = function() {
155 | return this.options_;
156 | };
157 | /**
158 | * Copyright 2013 Google Inc. All Rights Reserved.
159 | *
160 | * Licensed under the Apache License, Version 2.0 (the "License");
161 | * you may not use this file except in compliance with the License.
162 | * You may obtain a copy of the License at
163 | *
164 | * http://www.apache.org/licenses/LICENSE-2.0
165 | *
166 | * Unless required by applicable law or agreed to in writing, software
167 | * distributed under the License is distributed on an "AS IS" BASIS,
168 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
169 | * See the License for the specific language governing permissions and
170 | * limitations under the License.
171 | */
172 |
173 | /**
174 | * A simple cluster class representing a number of ClusterItems clustered at a
175 | * single position.
176 | * @param {!google.maps.LatLng} position
177 | * @param {!Array.} items
178 | * @constructor
179 | * @implements {Cluster}
180 | */
181 | function DefaultCluster(position, items) {
182 | // TODO(bckenny): add support for {lat: num, lng: num}
183 | /**
184 | * Position of cluster.
185 | * @private {!google.maps.LatLng}
186 | */
187 | this.position_ = position;
188 |
189 | /**
190 | * Items in cluster.
191 | * @private {!Array.}
192 | */
193 | this.items_ = items;
194 |
195 | /**
196 | * Number of items in cluster.
197 | * @private {number}
198 | */
199 | this.size_ = this.items_.length;
200 | }
201 |
202 | /**
203 | * @inheritDoc
204 | */
205 | DefaultCluster.prototype.getPosition = function() {
206 | return this.position_;
207 | };
208 |
209 | /**
210 | * @inheritDoc
211 | */
212 | DefaultCluster.prototype.getItems = function() {
213 | return this.items_;
214 | };
215 |
216 | /**
217 | * @inheritDoc
218 | */
219 | DefaultCluster.prototype.getSize = function() {
220 | return this.size_;
221 | };
222 | /**
223 | * Copyright 2013 Google Inc. All Rights Reserved.
224 | *
225 | * Licensed under the Apache License, Version 2.0 (the "License");
226 | * you may not use this file except in compliance with the License.
227 | * You may obtain a copy of the License at
228 | *
229 | * http://www.apache.org/licenses/LICENSE-2.0
230 | *
231 | * Unless required by applicable law or agreed to in writing, software
232 | * distributed under the License is distributed on an "AS IS" BASIS,
233 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
234 | * See the License for the specific language governing permissions and
235 | * limitations under the License.
236 | */
237 |
238 | /**
239 | * A very simple cluster renderer. When clusters change, existing ones are
240 | * repurposed, new ones are created as needed, and unneeded ones are removed
241 | * from map.
242 | * @param {!google.maps.Map} map
243 | * @constructor
244 | * @implements {ClusterRenderer}
245 | */
246 | function DefaultClusterRenderer(map) {
247 | /**
248 | * The map for this renderer.
249 | * @private {!google.maps.Map}
250 | */
251 | this.map_ = map;
252 |
253 | /**
254 | * A pool of markers used for display and expanded on demand.
255 | * @private {!Array.}
256 | */
257 | this.markers_ = [];
258 | }
259 |
260 | /**
261 | * A color scale hack.
262 | * @private {number}
263 | * @const
264 | */
265 | DefaultClusterRenderer.SIZE_COLOR_SCALE_ = 30;
266 |
267 | /**
268 | * @inheritDoc
269 | */
270 | DefaultClusterRenderer.prototype.onClustersChanged = function(clusters) {
271 | // reuse markers from existing pool, expanding pool as needed
272 | for (var i = 0; i < clusters.length; i++) {
273 | var marker;
274 | if (i < this.markers_.length) {
275 | marker = this.markers_[i];
276 | if (!marker.getMap()) {
277 | marker.setMap(this.map_);
278 | }
279 | } else {
280 | marker = new google.maps.Marker({map: this.map_});
281 | this.markers_[i] = marker;
282 | }
283 |
284 | marker.setPosition(clusters[i].getPosition());
285 |
286 | var colorScale = clusters[i].getSize() /
287 | DefaultClusterRenderer.SIZE_COLOR_SCALE_;
288 | colorScale = Math.min(1, Math.sqrt(colorScale));
289 | var red = Math.min(Math.round(255 * colorScale), 255);
290 | marker.setIcon(/** @type {google.maps.Symbol} */ ({
291 | path: google.maps.SymbolPath.CIRCLE,
292 | scale: 10,
293 | strokeWeight: 4,
294 | fillColor: 'rgb(' + red + ',0,0)',
295 | fillOpacity: 1
296 | }));
297 | }
298 |
299 | // remove any member of the marker pool that was unused
300 | for (; i < this.markers_.length; i++) {
301 | if (this.markers_[i].getMap()) {
302 | this.markers_[i].setMap(null);
303 | }
304 | }
305 | };
306 | /**
307 | * Copyright 2013 Google Inc. All Rights Reserved.
308 | *
309 | * Licensed under the Apache License, Version 2.0 (the "License");
310 | * you may not use this file except in compliance with the License.
311 | * You may obtain a copy of the License at
312 | *
313 | * http://www.apache.org/licenses/LICENSE-2.0
314 | *
315 | * Unless required by applicable law or agreed to in writing, software
316 | * distributed under the License is distributed on an "AS IS" BASIS,
317 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
318 | * See the License for the specific language governing permissions and
319 | * limitations under the License.
320 | */
321 |
322 | /**
323 | * The required interface for values to be stored in a k-d tree.
324 | * @interface
325 | */
326 | function KdData() {}
327 |
328 | /**
329 | * The x coordinate of the value.
330 | * @type {number}
331 | */
332 | KdData.prototype.x;
333 |
334 | /**
335 | * The y coordinate of the value.
336 | * @type {number}
337 | */
338 | KdData.prototype.y;
339 |
340 | /**
341 | * An index for disambiguating values at the same (x, y) coordinates.
342 | * @type {number}
343 | */
344 | KdData.prototype.index;
345 |
346 | /**
347 | * A node in a k-d tree.
348 | * @param {KdData} data
349 | * @constructor
350 | */
351 | function KdNode(data) {
352 | this.left = null;
353 | this.right = null;
354 | this.data = data;
355 | }
356 |
357 | /**
358 | * An object representing a nearest neighbor.
359 | * @interface
360 | */
361 | function KdNearestNeighbor() {}
362 |
363 | /**
364 | * @type {KdData}
365 | */
366 | KdNearestNeighbor.prototype.neighbor;
367 |
368 | /**
369 | * @type {number}
370 | */
371 | KdNearestNeighbor.prototype.distance;
372 | /**
373 | * Copyright 2013 Google Inc. All Rights Reserved.
374 | *
375 | * Licensed under the Apache License, Version 2.0 (the "License");
376 | * you may not use this file except in compliance with the License.
377 | * You may obtain a copy of the License at
378 | *
379 | * http://www.apache.org/licenses/LICENSE-2.0
380 | *
381 | * Unless required by applicable law or agreed to in writing, software
382 | * distributed under the License is distributed on an "AS IS" BASIS,
383 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
384 | * See the License for the specific language governing permissions and
385 | * limitations under the License.
386 | */
387 |
388 | /* global KdNode */
389 |
390 | /**
391 | * A k-d tree data structure. The stored KdData objects are used directly and
392 | * are *not* copied, so any change to their properties will invalidate the
393 | * results produced by this class. The KdData's x and y coordinates are used
394 | * for spatial sorting; its index is used to disambiguate points at the same
395 | * location. If two values in the tree have the same x, y, and index values,
396 | * results may be incorrect and an execution error may occur.
397 | * @param {Array.=} opt_points Initial points for tree. The array
398 | * itself is not retained and may be reused.
399 | * @constructor
400 | */
401 | function KdTree(opt_points) {
402 | var points = opt_points || [];
403 |
404 | var xsorted = [];
405 | var ysorted = [];
406 | for (var i = 0; i < points.length; i++) {
407 | xsorted[i] = points[i];
408 | ysorted[i] = points[i];
409 | }
410 | xsorted.sort(KdTree.compareX_);
411 | ysorted.sort(KdTree.compareY_);
412 |
413 | this.root = KdTree.build_(xsorted, ysorted, true);
414 | }
415 |
416 | /**
417 | * Comparator function for sorting KdData values in x. If two objects have the
418 | * same x value, they are sorted in y, and if equal there, by unique index.
419 | * @param {KdData} a
420 | * @param {KdData} b
421 | * @return {number}
422 | * @private
423 | */
424 | KdTree.compareX_ = function(a, b) {
425 | if (a.x < b.x) {
426 | return -1;
427 | } else if (a.x === b.x) {
428 | if (a.y < b.y) {
429 | return -1;
430 | } else if (a.y === b.y) {
431 | if (a.index < b.index) {
432 | return -1;
433 | } else if (a.index === b.index) {
434 | return 0;
435 | }
436 | }
437 | }
438 | return 1;
439 | };
440 |
441 | /**
442 | * Returns true if a is strictly less than b in x, otherwise false. See
443 | * KdTree.compareX_ for ordering details.
444 | * @param {KdData} a
445 | * @param {KdData} b
446 | * @return {boolean}
447 | * @private
448 | */
449 | KdTree.lessThanX_ = function(a, b) {
450 | return (a.x < b.x) || (a.x === b.x && ((a.y < b.y) || (a.y === b.y && a.index < b.index)));
451 | };
452 |
453 | /**
454 | * Comparator function for sorting KdData values in y. If two objects have the
455 | * same y value, they are sorted in x, and if equal there, by unique index.
456 | * @param {KdData} a
457 | * @param {KdData} b
458 | * @return {number}
459 | * @private
460 | */
461 | KdTree.compareY_ = function(a, b) {
462 | if (a.y < b.y) {
463 | return -1;
464 | } else if (a.y === b.y) {
465 | if (a.x < b.x) {
466 | return -1;
467 | } else if (a.x === b.x) {
468 | if (a.index < b.index) {
469 | return -1;
470 | } else if (a.index === b.index) {
471 | return 0;
472 | }
473 | }
474 | }
475 | return 1;
476 | };
477 |
478 | /**
479 | * Returns true if a is strictly less than b in y, otherwise false. See
480 | * KdTree.compareY_ for ordering details.
481 | * @param {KdData} a
482 | * @param {KdData} b
483 | * @return {boolean}
484 | * @private
485 | */
486 | KdTree.lessThanY_ = function(a, b) {
487 | return (a.y < b.y) || (a.y === b.y && ((a.x < b.x) || (a.x === b.x && a.index < b.index)));
488 | };
489 |
490 | /**
491 | * Build a k-d tree from the provided values.
492 | * @param {!Array.} sorted0 An array of values to put in the tree,
493 | * sorted in the dimension indeicated by xLevel.
494 | * @param {!Array.} sorted1 An array of the same values, sorted in the
495 | * other dimension.
496 | * @param {boolean} xLevel True if x is the dimension to sort in at this level,
497 | * false if y.
498 | * @return {KdNode} The root of the tree created from values.
499 | * @private
500 | */
501 | KdTree.build_ = function(sorted0, sorted1, xLevel) {
502 | var node;
503 |
504 | // base cases
505 | if (sorted0.length === 1) {
506 | node = new KdNode(sorted0[0]);
507 | return node;
508 | } else if (sorted0.length === 0) {
509 | return null;
510 | }
511 |
512 | var medianIndex = (sorted0.length / 2) | 0;
513 | var median = sorted0[medianIndex];
514 | node = new KdNode(median);
515 |
516 | var left0 = [];
517 | var right0 = [];
518 | for (var i = 0; i < medianIndex; i++) {
519 | left0[i] = sorted0[i];
520 | }
521 | i++;
522 | for (var j = 0; i < sorted0.length; i++) {
523 | right0[j++] = sorted0[i];
524 | }
525 |
526 | var left1 = [];
527 | var right1 = [];
528 | j = 0;
529 | var k = 0;
530 | for (i = 0; i < sorted1.length; i++) {
531 | var point = sorted1[i];
532 | if (point === median) {
533 | continue;
534 | }
535 |
536 | if (xLevel) {
537 | if (KdTree.lessThanX_(point, median)) {
538 | left1[j++] = point;
539 | } else {
540 | right1[k++] = point;
541 | }
542 | } else {
543 | if (KdTree.lessThanY_(point, median)) {
544 | left1[j++] = point;
545 | } else {
546 | right1[k++] = point;
547 | }
548 | }
549 | }
550 |
551 | node.left = KdTree.build_(left1, left0, !xLevel);
552 | node.right = KdTree.build_(right1, right0, !xLevel);
553 |
554 | return node;
555 | };
556 |
557 | /**
558 | * Find the minimum value in the specified dimension.
559 | * @param {KdNode} node The root node of the search.
560 | * @param {boolean} inX If finding the minimum in x, true, in y, false.
561 | * @param {boolean} xLevel If node's children are sorted in x, true, in y,
562 | * false.
563 | * @return {KdData}
564 | * @private
565 | */
566 | KdTree.findMinimum_ = function(node, inX, xLevel) {
567 | if (node === null) {
568 | return null;
569 | }
570 |
571 | if (inX === xLevel) {
572 | if (!node.left) {
573 | return node.data;
574 | } else {
575 | return KdTree.findMinimum_(node.left, inX, !xLevel);
576 | }
577 |
578 | } else {
579 | // return minimum of left, right, and node
580 | var left = KdTree.findMinimum_(node.left, inX, !xLevel);
581 | var right = KdTree.findMinimum_(node.right, inX, !xLevel);
582 | var minimum = node.data;
583 |
584 | if (inX) {
585 | if (left && KdTree.lessThanX_(left, minimum)) {
586 | minimum = left;
587 | }
588 | if (right && KdTree.lessThanX_(right, minimum)) {
589 | minimum = right;
590 | }
591 | } else {
592 | if (left && KdTree.lessThanY_(left, minimum)) {
593 | minimum = left;
594 | }
595 | if (right && KdTree.lessThanY_(right, minimum)) {
596 | minimum = right;
597 | }
598 | }
599 |
600 | return minimum;
601 | }
602 | };
603 |
604 | /**
605 | * Find the maximum value in the specified dimension.
606 | * @param {KdNode} node The root node of the search.
607 | * @param {boolean} inX If finding the maximum in x, true, in y, false.
608 | * @param {boolean} xLevel If node's children are sorted in x, true, in y,
609 | * false.
610 | * @return {KdData}
611 | * @private
612 | */
613 | KdTree.findMaximum_ = function(node, inX, xLevel) {
614 | if (node === null) {
615 | return null;
616 | }
617 |
618 | if (inX === xLevel) {
619 | if (!node.right) {
620 | return node.data;
621 | } else {
622 | return KdTree.findMaximum_(node.right, inX, !xLevel);
623 | }
624 |
625 | } else {
626 | // return maximum of left, right, and node
627 | var left = KdTree.findMaximum_(node.left, inX, !xLevel);
628 | var right = KdTree.findMaximum_(node.right, inX, !xLevel);
629 | var maximum = node.data;
630 |
631 | // NOTE: left !== right !== node.data, so !lessThan works for greaterThan
632 | if (inX) {
633 | if (left && !KdTree.lessThanX_(left, maximum)) {
634 | maximum = left;
635 | }
636 | if (right && !KdTree.lessThanX_(right, maximum)) {
637 | maximum = right;
638 | }
639 | } else {
640 | if (left && !KdTree.lessThanY_(left, maximum)) {
641 | maximum = left;
642 | }
643 | if (right && !KdTree.lessThanY_(right, maximum)) {
644 | maximum = right;
645 | }
646 | }
647 |
648 | return maximum;
649 | }
650 | };
651 |
652 | /**
653 | * Search for a value in the tree starting at specified root, removing it if
654 | * found, and returning the root (the new one, if changed).
655 | * @param {KdData} item The value to remove.
656 | * @param {KdNode} root The root node of the search for item.
657 | * @param {boolean} xLevel If root's children are sorted in x, true, in y,
658 | * false.
659 | * @return {KdNode}
660 | * @private
661 | */
662 | KdTree.delete_ = function(item, root, xLevel) {
663 | // TODO(bckenny): currently ~10% of execution time (including findMin/Max)
664 | if (root === null) {
665 | // TODO(bckenny): not sure what to do in this case yet
666 |
667 | } else if (root.data === item) {
668 | if (root.right !== null) {
669 | // take replacement from minimum on right
670 | root.data = KdTree.findMinimum_(root.right, xLevel, !xLevel);
671 | root.right = KdTree.delete_(root.data, root.right, !xLevel);
672 | } else if (root.left !== null) {
673 | root.data = KdTree.findMaximum_(root.left, xLevel, !xLevel);
674 | root.left = KdTree.delete_(root.data, root.left, !xLevel);
675 | } else {
676 | // leaf, so just delete
677 | root.data = null;
678 | return null;
679 | }
680 |
681 | } else {
682 | // can't be equal, so use lessThan/greaterThan
683 | var lessThan = xLevel ? KdTree.lessThanX_(item, root.data) : KdTree.lessThanY_(item, root.data);
684 | if (lessThan) {
685 | root.left = KdTree.delete_(item, root.left, !xLevel);
686 | } else {
687 | root.right = KdTree.delete_(item, root.right, !xLevel);
688 | }
689 | }
690 |
691 | return root;
692 | };
693 |
694 | /**
695 | * Find the nearest value in the tree to target, as defined by comparator
696 | * sorting.
697 | * @param {KdData} target
698 | * @param {KdNode} root The root node of the search for a neighbor.
699 | * @param {boolean} xLevel If root's children are sorted in x, true, in y,
700 | * false.
701 | * @param {KdNearestNeighbor} candidate The closest neighbor so far.
702 | * @return {KdNearestNeighbor}
703 | * @private
704 | */
705 | KdTree.nearest_ = function(target, root, xLevel, candidate) {
706 | if (root === null) {
707 | return candidate;
708 | }
709 |
710 | var data = root.data;
711 | var xDiff = target.x - data.x;
712 | var yDiff = target.y - data.y;
713 | xDiff *= xDiff;
714 | yDiff *= yDiff;
715 |
716 | // test this node, but skip over if self
717 | if (target !== data) {
718 | var distance = xDiff + yDiff;
719 | if (distance < candidate.distance) {
720 | candidate.distance = distance;
721 | candidate.neighbor = data;
722 | }
723 | }
724 |
725 | // recurse to children, prioritizing the nearer half
726 | var lessThan;
727 | var testDistance;
728 | if (xLevel) {
729 | lessThan = target.x < data.x;
730 | testDistance = xDiff;
731 | } else {
732 | lessThan = target.y < data.y;
733 | testDistance = yDiff;
734 | }
735 |
736 | if (lessThan) {
737 | candidate = KdTree.nearest_(target, root.left, !xLevel, candidate);
738 | if (root.right && testDistance <= candidate.distance) {
739 | candidate = KdTree.nearest_(target, root.right, !xLevel, candidate);
740 | }
741 |
742 | } else {
743 | candidate = KdTree.nearest_(target, root.right, !xLevel, candidate);
744 | if (root.left && testDistance <= candidate.distance) {
745 | candidate = KdTree.nearest_(target, root.left, !xLevel, candidate);
746 | }
747 | }
748 |
749 | return candidate;
750 | };
751 |
752 | /**
753 | * Add a value to the k-d tree.
754 | * @param {KdData} item
755 | */
756 | KdTree.prototype.addItem = function(item) {
757 | if (!this.root) {
758 | this.root = new KdNode(item);
759 | return;
760 | }
761 |
762 | var node = this.root;
763 | var xLevel = true;
764 |
765 | while (true) {
766 | var lessThan = xLevel ? KdTree.lessThanX_(item, node.data) : KdTree.lessThanY_(item, node.data);
767 |
768 | if (item === node.data) {
769 | throw new Error('duplicate entry by our metric');
770 |
771 | } else if (lessThan) {
772 | if (!node.left) {
773 | node.left = new KdNode(item);
774 | return;
775 | }
776 | node = node.left;
777 | xLevel = !xLevel;
778 |
779 | } else {
780 | if (!node.right) {
781 | node.right = new KdNode(item);
782 | return;
783 | }
784 | node = node.right;
785 | xLevel = !xLevel;
786 | }
787 | }
788 | };
789 |
790 | /**
791 | * Remove an item from the k-d tree, if present.
792 | * @param {KdData} item
793 | */
794 | KdTree.prototype.removeItem = function(item) {
795 | this.root = KdTree.delete_(item, this.root, true);
796 | };
797 |
798 | /**
799 | * Find the nearest neighbor to item in the tree.
800 | * @param {KdData} item
801 | * @return {KdNearestNeighbor}
802 | */
803 | KdTree.prototype.nearestNeighbor = function(item) {
804 | var candidate = /** @type {KdNearestNeighbor} */ ({
805 | neighbor: null,
806 | distance: Number.MAX_VALUE
807 | });
808 |
809 | return KdTree.nearest_(item, this.root, true, candidate);
810 | };
811 | /**
812 | * Copyright 2013 Google Inc. All Rights Reserved.
813 | *
814 | * Licensed under the Apache License, Version 2.0 (the "License");
815 | * you may not use this file except in compliance with the License.
816 | * You may obtain a copy of the License at
817 | *
818 | * http://www.apache.org/licenses/LICENSE-2.0
819 | *
820 | * Unless required by applicable law or agreed to in writing, software
821 | * distributed under the License is distributed on an "AS IS" BASIS,
822 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
823 | * See the License for the specific language governing permissions and
824 | * limitations under the License.
825 | */
826 |
827 | /**
828 | * A prioritizable node.
829 | * @interface
830 | *
831 | */
832 | function PriorityQueueNode() {}
833 |
834 | /**
835 | * @type {number}
836 | */
837 | PriorityQueueNode.prototype.value;
838 | /**
839 | * Copyright 2013 Google Inc. All Rights Reserved.
840 | *
841 | * Licensed under the Apache License, Version 2.0 (the "License");
842 | * you may not use this file except in compliance with the License.
843 | * You may obtain a copy of the License at
844 | *
845 | * http://www.apache.org/licenses/LICENSE-2.0
846 | *
847 | * Unless required by applicable law or agreed to in writing, software
848 | * distributed under the License is distributed on an "AS IS" BASIS,
849 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
850 | * See the License for the specific language governing permissions and
851 | * limitations under the License.
852 | */
853 |
854 | /**
855 | * Implementation of a min-priority queue. If initial values are provided via
856 | * opt_elements, the queue will use that array as storage, modifying it in
857 | * place, assuming total ownership afterwards.
858 | * @param {Array.=} opt_elements A set of elements to
859 | * intialize the queue with.
860 | * @constructor
861 | */
862 | function MinPriorityQueue(opt_elements) {
863 | /**
864 | * The heap.
865 | * @private {!Array.}
866 | */
867 | this.elements_ = opt_elements || [];
868 |
869 | /**
870 | * The number of elements in the heap.
871 | * @private {number}
872 | */
873 | this.size_ = this.elements_.length;
874 |
875 | if (opt_elements) {
876 | this.init_();
877 | }
878 | }
879 |
880 | /**
881 | * Ensure that the node at index correctly fulfills heap property with respect
882 | * to its children.
883 | * @param {number} index The index of the node to check
884 | * @private
885 | */
886 | MinPriorityQueue.prototype.heapify_ = function(index) {
887 | var elements = this.elements_;
888 | var minIndex = index;
889 | var item = elements[index];
890 | var itemValue = item.value;
891 | var minItem = item;
892 |
893 | while (true) {
894 | var leftIndex = index * 2 + 1;
895 | if (leftIndex < this.size_) {
896 | var left = elements[leftIndex];
897 | if (left.value < itemValue) {
898 | minIndex = leftIndex;
899 | minItem = left;
900 | }
901 |
902 | var rightIndex = leftIndex + 1;
903 | if (rightIndex < this.size_) {
904 | var right = elements[rightIndex];
905 | if (right.value < minItem.value) {
906 | minIndex = rightIndex;
907 | minItem = right;
908 | }
909 | }
910 |
911 | if (minIndex !== index) {
912 | elements[index] = minItem;
913 | index = minIndex;
914 | } else {
915 | elements[index] = item;
916 | break;
917 | }
918 | } else {
919 | elements[index] = item;
920 | break;
921 | }
922 | }
923 | };
924 |
925 | /**
926 | * Ensure that all nodes fulfill the heap property.
927 | * @private
928 | */
929 | MinPriorityQueue.prototype.init_ = function() {
930 | var last = this.size_ - 1;
931 | var index = Math.floor((last - 1) / 2);
932 |
933 | for (; index >= 0; index--) {
934 | this.heapify_(index);
935 | }
936 | };
937 |
938 | /**
939 | * Returns the current number of elements in the queue.
940 | * @return {number}
941 | */
942 | MinPriorityQueue.prototype.getSize = function() {
943 | return this.size_;
944 | };
945 |
946 | /**
947 | * Returns the minimum element, if any, without removing from the
948 | * queue.
949 | * @return {PriorityQueueNode|undefined}
950 | */
951 | MinPriorityQueue.prototype.peek = function() {
952 | return this.elements_[0];
953 | };
954 |
955 | /**
956 | * Removes the minimum element from the queue, if any, and returns it.
957 | * @return {PriorityQueueNode|undefined}
958 | */
959 | MinPriorityQueue.prototype.pop = function() {
960 | var min = this.elements_[0];
961 | this.size_--;
962 | this.elements_[0] = this.elements_[this.size_];
963 | this.elements_[this.size_] = null;
964 | this.heapify_(0);
965 |
966 | return min;
967 | };
968 |
969 | /**
970 | * Puts the specified element into the queue.
971 | * @param {!PriorityQueueNode} element
972 | */
973 | MinPriorityQueue.prototype.push = function(element) {
974 | var elements = this.elements_;
975 | var index = this.size_;
976 | elements[index] = element;
977 | this.size_++;
978 |
979 | var parentIndex = Math.floor((index - 1) / 2);
980 | while (parentIndex >= 0 && elements[index].value < elements[parentIndex].value) {
981 | var tmp = elements[parentIndex];
982 | elements[parentIndex] = elements[index];
983 | elements[index] = tmp;
984 |
985 | index = parentIndex;
986 | parentIndex = Math.floor((index - 1) / 2);
987 | }
988 | };
989 | /**
990 | * Copyright 2013 Google Inc. All Rights Reserved.
991 | *
992 | * Licensed under the Apache License, Version 2.0 (the "License");
993 | * you may not use this file except in compliance with the License.
994 | * You may obtain a copy of the License at
995 | *
996 | * http://www.apache.org/licenses/LICENSE-2.0
997 | *
998 | * Unless required by applicable law or agreed to in writing, software
999 | * distributed under the License is distributed on an "AS IS" BASIS,
1000 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1001 | * See the License for the specific language governing permissions and
1002 | * limitations under the License.
1003 | */
1004 |
1005 | /**
1006 | * An object for the internal representation of a cluster.
1007 | * @param {number} x The x coordinate of the cluster.
1008 | * @param {number} sumX The sum of x coordinates of the clustered objects.
1009 | * @param {number} y The y coordinate of the cluster.
1010 | * @param {number} sumY The sum of y coordinates of the clustered objects.
1011 | * @param {!Array.} indices The indices of the clustered objects.
1012 | * @param {number} index The index of this object in array of clusters.
1013 | * @constructor
1014 | * @implements {KdData}
1015 | * @struct
1016 | */
1017 | function ClusterObject(x, sumX, y, sumY, indices, index) {
1018 | /**
1019 | * @type {number}
1020 | */
1021 | this.x = x;
1022 |
1023 | /**
1024 | * @type {number}
1025 | */
1026 | this.sumX = sumX;
1027 |
1028 | /**
1029 | * @type {number}
1030 | */
1031 | this.y = y;
1032 |
1033 | /**
1034 | * @type {number}
1035 | */
1036 | this.sumY = sumY;
1037 |
1038 | /**
1039 | * @type {!Array.}
1040 | */
1041 | this.indices = indices;
1042 |
1043 | /**
1044 | * @type {boolean}
1045 | */
1046 | this.valid = true;
1047 |
1048 | /**
1049 | * @type {number}
1050 | */
1051 | this.index = index;
1052 | }
1053 | /**
1054 | * Copyright 2013 Google Inc. All Rights Reserved.
1055 | *
1056 | * Licensed under the Apache License, Version 2.0 (the "License");
1057 | * you may not use this file except in compliance with the License.
1058 | * You may obtain a copy of the License at
1059 | *
1060 | * http://www.apache.org/licenses/LICENSE-2.0
1061 | *
1062 | * Unless required by applicable law or agreed to in writing, software
1063 | * distributed under the License is distributed on an "AS IS" BASIS,
1064 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1065 | * See the License for the specific language governing permissions and
1066 | * limitations under the License.
1067 | */
1068 |
1069 | /**
1070 | * An edge between a node and possibly the next closest node.
1071 | * @interface
1072 | * @extends {PriorityQueueNode}
1073 | */
1074 | function ClusterEdge() {}
1075 |
1076 | /**
1077 | * The origin of the edge, always defined.
1078 | * @type {!ClusterObject}
1079 | */
1080 | ClusterEdge.prototype.origin;
1081 |
1082 | /**
1083 | * The destination of the edge, if one exists.
1084 | * @type {ClusterObject}
1085 | */
1086 | ClusterEdge.prototype.dest;
1087 |
1088 | /**
1089 | * The value of the edge, in this case the square of the length between nodes.
1090 | */
1091 | ClusterEdge.prototype.value;
1092 | /**
1093 | * Copyright 2013 Google Inc. All Rights Reserved.
1094 | *
1095 | * Licensed under the Apache License, Version 2.0 (the "License");
1096 | * you may not use this file except in compliance with the License.
1097 | * You may obtain a copy of the License at
1098 | *
1099 | * http://www.apache.org/licenses/LICENSE-2.0
1100 | *
1101 | * Unless required by applicable law or agreed to in writing, software
1102 | * distributed under the License is distributed on an "AS IS" BASIS,
1103 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1104 | * See the License for the specific language governing permissions and
1105 | * limitations under the License.
1106 | */
1107 |
1108 | /* global DefaultCluster, KdTree, MinPriorityQueue, ClusterObject */
1109 |
1110 | /**
1111 | * A hierarchical clustering algorithm. Clusters any items a set distance apart
1112 | * in pixel space, in order by distance (ascending). The clustering order of
1113 | * multiple pairs of items an equal distance apart is defined by insertion
1114 | * order and the details of implementation, so may vary between releases.
1115 | * Assumes pixel space is defined by the Web Mercator projection.
1116 | * @param {number=} opt_clusterDistance The distance within which two objects
1117 | * will clustered. If not specified, a default of 30 piexls will be used.
1118 | * @constructor
1119 | * @implements {ClusterAlgorithm}
1120 | */
1121 | function HierarchicalClusterer(opt_clusterDistance) {
1122 | /**
1123 | * The objects to be clustered.
1124 | * @private {!Array.}
1125 | */
1126 | this.items_ = [];
1127 |
1128 | /**
1129 | * The count of objects to be clustered.
1130 | * @private {number}
1131 | */
1132 | this.itemCount_ = 0;
1133 |
1134 | /**
1135 | * The distance within which two objects will be clustered.
1136 | * @private {number}
1137 | */
1138 | this.clusterDistance_ = (opt_clusterDistance != null) ? opt_clusterDistance :
1139 | HierarchicalClusterer.CLUSTER_PIXEL_DISTANCE;
1140 | }
1141 |
1142 | /**
1143 | * The default distance within which two objects will clustered.
1144 | * @type {number}
1145 | * @const
1146 | */
1147 | HierarchicalClusterer.CLUSTER_PIXEL_DISTANCE = 30;
1148 |
1149 | /**
1150 | * Convert longitude to x in world coordinates.
1151 | * @param {number} lng
1152 | * @return {number}
1153 | * @private
1154 | */
1155 | HierarchicalClusterer.lngToX_ = function(lng) {
1156 | return 256 * (lng / 360 + 0.5);
1157 | };
1158 |
1159 | /**
1160 | * Convert latitude to y in world coordinates.
1161 | * @param {number} lat
1162 | * @return {number}
1163 | * @private
1164 | */
1165 | HierarchicalClusterer.latToY_ = function(lat) {
1166 | var merc = -Math.log(Math.tan((0.25 + lat / 360) * Math.PI));
1167 | return 128 * (1 + merc / Math.PI);
1168 | };
1169 |
1170 | /**
1171 | * Convert x in world coordinates to longitude.
1172 | * @param {number} x
1173 | * @return {number}
1174 | * @private
1175 | */
1176 | HierarchicalClusterer.xToLng_ = function(x) {
1177 | return 360 * (x / 256 - 0.5);
1178 | };
1179 |
1180 | /**
1181 | * Convert y in world coordinates to latitude.
1182 | * @param {number} y
1183 | * @return {number}
1184 | * @private
1185 | */
1186 | HierarchicalClusterer.yToLat_ = function(y) {
1187 | var merc = Math.PI * (y / 128 - 1);
1188 | return (360 / Math.PI) * Math.atan(Math.exp(-merc)) - 90;
1189 | };
1190 |
1191 | /**
1192 | * @inheritDoc
1193 | */
1194 | HierarchicalClusterer.prototype.addItem = function(item) {
1195 | var latLng = item.getPosition();
1196 | var newPoint = /** @type {KdTreeQueueItem} */ ({
1197 | x: HierarchicalClusterer.lngToX_(latLng.lng()),
1198 | y: HierarchicalClusterer.latToY_(latLng.lat()),
1199 | item: item
1200 | });
1201 |
1202 | this.items_[this.itemCount_] = newPoint;
1203 | this.itemCount_++;
1204 | };
1205 |
1206 | /**
1207 | * @inheritDoc
1208 | */
1209 | HierarchicalClusterer.prototype.addItems = function(items) {
1210 | for (var i = 0; i < items.length; i++) {
1211 | this.addItem(items[i]);
1212 | }
1213 | };
1214 |
1215 | /**
1216 | * @inheritDoc
1217 | */
1218 | HierarchicalClusterer.prototype.clearItems = function() {
1219 | this.items_ = [];
1220 | this.itemCount_ = 0;
1221 | };
1222 |
1223 | /**
1224 | * @inheritDoc
1225 | */
1226 | HierarchicalClusterer.prototype.getItems = function() {
1227 | var itemArray = [];
1228 | for (var i = 0; i < this.items_.length; i++) {
1229 | itemArray[i] = this.items_[i];
1230 | }
1231 |
1232 | return itemArray;
1233 | };
1234 |
1235 | /**
1236 | * @inheritDoc
1237 | */
1238 | HierarchicalClusterer.prototype.removeItem = function(item) {
1239 | // item order doesn't matter, so replace removed with last element
1240 | // removes first matching item and keeps array dense
1241 | for (var i = 0; i < this.itemCount_; i++) {
1242 | if (this.items_[i].item === item){
1243 | this.itemCount_--;
1244 | this.items_[i] = this.items_[this.itemCount_];
1245 | this.items_[this.itemCount_] = null;
1246 | break;
1247 | }
1248 | }
1249 | };
1250 |
1251 | /**
1252 | * @inheritDoc
1253 | */
1254 | HierarchicalClusterer.prototype.getClusters = function(zoom) {
1255 | var cluster;
1256 |
1257 | var clusters = [];
1258 | for (var i = 0; i < this.itemCount_; i++) {
1259 | cluster = new ClusterObject(this.items_[i].x, this.items_[i].x,
1260 | this.items_[i].y, this.items_[i].y, [i], clusters.length);
1261 | clusters[i] = cluster;
1262 | }
1263 |
1264 | var epsilon = this.clusterDistance_ / (1 << zoom);
1265 |
1266 | var clusterCount = this.cluster_(clusters, epsilon);
1267 |
1268 | var finalClusters = [];
1269 | for (i = 0; i < clusterCount; i++) {
1270 | cluster = clusters[i];
1271 | var indices = cluster.indices;
1272 | var latLng;
1273 | if (indices.length === 1) {
1274 | // single point, so just reuse latlng
1275 | latLng = this.items_[indices[0]].item.getPosition();
1276 | } else {
1277 | var lat = HierarchicalClusterer.yToLat_(cluster.y);
1278 | var lng = HierarchicalClusterer.xToLng_(cluster.x);
1279 | latLng = new google.maps.LatLng(lat, lng);
1280 | }
1281 |
1282 | var items = [];
1283 | for (var j = 0; j < cluster.indices.length; j++) {
1284 | items[j] = this.items_[cluster.indices[j]].item;
1285 | }
1286 |
1287 | finalClusters[i] = new DefaultCluster(latLng, items);
1288 | }
1289 |
1290 | return finalClusters;
1291 | };
1292 |
1293 | /**
1294 | * Cluster the specified objects, using minDistance as a threshold. The
1295 | * clustering is done within the clusters array; the returned value is the
1296 | * number of the resulting clusters, found in that number of the first slots of
1297 | * the clusters array.
1298 | * @param {!Array.} clusters The objects to cluster.
1299 | * @param {number} minDistance
1300 | * @return {number} The number of resulting clusters.
1301 | * @private
1302 | */
1303 | HierarchicalClusterer.prototype.cluster_ = function(clusters, minDistance) {
1304 | var kdtree = new KdTree(clusters);
1305 | var edgeQueue = this.createShortestEdgeQueue_(clusters, kdtree);
1306 |
1307 | // merge clusters if within limit
1308 | var clusterCount = clusters.length;
1309 | var sqEpsilon = minDistance * minDistance;
1310 | while (edgeQueue.peek().value < sqEpsilon && clusterCount > 1) {
1311 | var shortest = edgeQueue.pop();
1312 |
1313 | if (shortest.origin.valid) {
1314 | var originCluster;
1315 | var originClusterIndex;
1316 |
1317 | if (!shortest.dest.valid) {
1318 | // dest not valid, so need to find new dest
1319 | originCluster = shortest.origin;
1320 | originClusterIndex = originCluster.index;
1321 |
1322 | } else {
1323 | // origin and dest valid, combine into new cluster and find new dest
1324 | var item0 = shortest.origin;
1325 | var item1 = shortest.dest;
1326 |
1327 | var indices = item0.indices;
1328 | var start = indices.length;
1329 | var oldIndices = item1.indices;
1330 | var length = oldIndices.length;
1331 | for (var i = 0; i < length; i++) {
1332 | indices[start + i] = oldIndices[i];
1333 | }
1334 |
1335 | var sumX = item0.sumX + item1.sumX;
1336 | var sumY = item0.sumY + item1.sumY;
1337 | var newX = sumX / indices.length;
1338 | var newY = sumY / indices.length;
1339 |
1340 | // eliminate old clusters
1341 | item0.valid = false;
1342 | kdtree.removeItem(item0);
1343 | item1.valid = false;
1344 | kdtree.removeItem(item1);
1345 | originClusterIndex = Math.min(item0.index, item1.index);
1346 | var oldIndex = Math.max(item0.index, item1.index);
1347 | clusterCount--;
1348 | clusters[oldIndex] = clusters[clusterCount];
1349 | clusters[oldIndex].index = oldIndex;
1350 | clusters[clusterCount] = null;
1351 |
1352 | originCluster = new ClusterObject(newX, sumX, newY, sumY,
1353 | indices, originClusterIndex);
1354 |
1355 | clusters[originClusterIndex] = originCluster;
1356 | kdtree.addItem(originCluster);
1357 | }
1358 | if (clusterCount < 2) {
1359 | break;
1360 | }
1361 |
1362 | // update edge by finding closest cluster dest to new origin
1363 | shortest.origin = originCluster;
1364 | var newNearest = kdtree.nearestNeighbor(originCluster);
1365 | shortest.dest = newNearest.neighbor;
1366 | shortest.value = newNearest.distance;
1367 |
1368 | // put new edge back on queue
1369 | edgeQueue.push(shortest);
1370 | }
1371 | }
1372 |
1373 | return clusterCount;
1374 | };
1375 |
1376 | /**
1377 | * Creates a priority queue of edges, one per cluster, representing the
1378 | * distance to the closest other cluster.
1379 | * @param {!Array.} clusters
1380 | * @param {KdTree} kdtree An acceleration data structure for fast nearest-
1381 | * neighbor queries, already populated by clusters array.
1382 | * @return {MinPriorityQueue}
1383 | * @private
1384 | */
1385 | HierarchicalClusterer.prototype.createShortestEdgeQueue_ =
1386 | function(clusters, kdtree) {
1387 | var edges = [];
1388 | for (var i = 0; i < clusters.length; i++) {
1389 | var cluster = clusters[i];
1390 | var nearest = kdtree.nearestNeighbor(cluster);
1391 |
1392 | var edge = /** @type {ClusterEdge} */ ({
1393 | origin: cluster,
1394 | dest: nearest.neighbor,
1395 | value: nearest.distance
1396 | });
1397 | edges[i] = edge;
1398 | }
1399 |
1400 | return new MinPriorityQueue(edges);
1401 | };
1402 |
--------------------------------------------------------------------------------
/src/clustering/build/clusterer.min.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2013 Google Inc. All Rights Reserved.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 | function m(a){return function(){return this[a]}}var n;function q(a){this.c=a;this.a=new s;this.b=new t(a)}n=q.prototype;n.o=function(a){this.b=a};n.n=function(a){var b=this.a.getItems();this.a=a;this.a.addItems(b)};n.h=function(a){this.a.addItem(a)};n.i=function(a){this.a.addItems(a)};n.j=function(){this.a.clearItems()};n.k=function(a){this.a.removeItem(a)};n.l=function(){var a=this.a.getClusters(this.c.getZoom());this.b.onClustersChanged(a)};n.m=function(){};function u(a,b){this.a=a;this.c=b}
18 | u.prototype.getPosition=m("a");u.prototype.b=m("c");function v(a,b){this.b=a;this.a=b;this.c=this.a.length}v.prototype.getPosition=m("b");v.prototype.getItems=m("a");v.prototype.getSize=m("c");function t(a){this.b=a;this.a=[]}
19 | t.prototype.onClustersChanged=function(a){for(var b=0;ba)break;e.origin=g;
31 | g=H(g,b.a,!0,{e:null,distance:Number.MAX_VALUE});e.a=g.e;e.value=g.distance;h=d;g=h.b;f=h.a;g[f]=e;h.a++;for(e=Math.floor((f-1)/2);0<=e&&g[f].value build/clusterer.cat.js
4 |
5 | if [ -z $CLOSURE_COMPILER ]; then
6 | echo "CLOSURE_COMPILER should be defined."
7 | exit 1
8 | fi
9 |
10 | #TODO(bckenny): disable function inlining
11 | java -jar $CLOSURE_COMPILER --compilation_level ADVANCED_OPTIMIZATIONS --use_types_for_optimization --js=build/clusterer.cat.js --js=build/clusterer_exports.js --js_output_file=build/clusterer.min.js --warning_level=VERBOSE --jscomp_warning=accessControls --jscomp_warning=const --jscomp_warning=visibility --externs build/google_maps_api_v3_14.js --externs ClusterItem.js --externs Cluster.js --externs ClusterAlgorithm.js --externs ClusterRenderer.js --externs kdqueue/KdTreeQueueItem.js
12 | cd build/
13 |
14 | # from yepnope.js
15 | m=$(ls -la clusterer.min.js | awk '{ print $5}')
16 | gzip -nfc --best clusterer.min.js > clusterer.min.js.gz
17 | g=$(ls -la clusterer.min.js.gz | awk '{ print $5}')
18 | echo "$m bytes minified, $g bytes gzipped"
19 | rm clusterer.min.js.gz
--------------------------------------------------------------------------------
/src/clustering/kdqueue/ClusterEdge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * An edge between a node and possibly the next closest node.
19 | * @interface
20 | * @extends {PriorityQueueNode}
21 | */
22 | function ClusterEdge() {}
23 |
24 | /**
25 | * The origin of the edge, always defined.
26 | * @type {!ClusterObject}
27 | */
28 | ClusterEdge.prototype.origin;
29 |
30 | /**
31 | * The destination of the edge, if one exists.
32 | * @type {ClusterObject}
33 | */
34 | ClusterEdge.prototype.dest;
35 |
36 | /**
37 | * The value of the edge, in this case the square of the length between nodes.
38 | */
39 | ClusterEdge.prototype.value;
40 |
--------------------------------------------------------------------------------
/src/clustering/kdqueue/ClusterObject.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * An object for the internal representation of a cluster.
19 | * @param {number} x The x coordinate of the cluster.
20 | * @param {number} sumX The sum of x coordinates of the clustered objects.
21 | * @param {number} y The y coordinate of the cluster.
22 | * @param {number} sumY The sum of y coordinates of the clustered objects.
23 | * @param {!Array.} indices The indices of the clustered objects.
24 | * @param {number} index The index of this object in array of clusters.
25 | * @constructor
26 | * @implements {KdData}
27 | * @struct
28 | */
29 | function ClusterObject(x, sumX, y, sumY, indices, index) {
30 | /**
31 | * @type {number}
32 | */
33 | this.x = x;
34 |
35 | /**
36 | * @type {number}
37 | */
38 | this.sumX = sumX;
39 |
40 | /**
41 | * @type {number}
42 | */
43 | this.y = y;
44 |
45 | /**
46 | * @type {number}
47 | */
48 | this.sumY = sumY;
49 |
50 | /**
51 | * @type {!Array.}
52 | */
53 | this.indices = indices;
54 |
55 | /**
56 | * @type {boolean}
57 | */
58 | this.valid = true;
59 |
60 | /**
61 | * @type {number}
62 | */
63 | this.index = index;
64 | }
65 |
--------------------------------------------------------------------------------
/src/clustering/kdqueue/HierarchicalClusterer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /* global DefaultCluster, KdTree, MinPriorityQueue, ClusterObject */
18 |
19 | /**
20 | * A hierarchical clustering algorithm. Clusters any items a set distance apart
21 | * in pixel space, in order by distance (ascending). The clustering order of
22 | * multiple pairs of items an equal distance apart is defined by insertion
23 | * order and the details of implementation, so may vary between releases.
24 | * Assumes pixel space is defined by the Web Mercator projection.
25 | * @param {number=} opt_clusterDistance The distance within which two objects
26 | * will clustered. If not specified, a default of 30 piexls will be used.
27 | * @constructor
28 | * @implements {ClusterAlgorithm}
29 | */
30 | function HierarchicalClusterer(opt_clusterDistance) {
31 | /**
32 | * The objects to be clustered.
33 | * @private {!Array.}
34 | */
35 | this.items_ = [];
36 |
37 | /**
38 | * The count of objects to be clustered.
39 | * @private {number}
40 | */
41 | this.itemCount_ = 0;
42 |
43 | /**
44 | * The distance within which two objects will be clustered.
45 | * @private {number}
46 | */
47 | this.clusterDistance_ = (opt_clusterDistance != null) ? opt_clusterDistance :
48 | HierarchicalClusterer.CLUSTER_PIXEL_DISTANCE;
49 | }
50 |
51 | /**
52 | * The default distance within which two objects will clustered.
53 | * @type {number}
54 | * @const
55 | */
56 | HierarchicalClusterer.CLUSTER_PIXEL_DISTANCE = 30;
57 |
58 | /**
59 | * Convert longitude to x in world coordinates.
60 | * @param {number} lng
61 | * @return {number}
62 | * @private
63 | */
64 | HierarchicalClusterer.lngToX_ = function(lng) {
65 | return 256 * (lng / 360 + 0.5);
66 | };
67 |
68 | /**
69 | * Convert latitude to y in world coordinates.
70 | * @param {number} lat
71 | * @return {number}
72 | * @private
73 | */
74 | HierarchicalClusterer.latToY_ = function(lat) {
75 | var merc = -Math.log(Math.tan((0.25 + lat / 360) * Math.PI));
76 | return 128 * (1 + merc / Math.PI);
77 | };
78 |
79 | /**
80 | * Convert x in world coordinates to longitude.
81 | * @param {number} x
82 | * @return {number}
83 | * @private
84 | */
85 | HierarchicalClusterer.xToLng_ = function(x) {
86 | return 360 * (x / 256 - 0.5);
87 | };
88 |
89 | /**
90 | * Convert y in world coordinates to latitude.
91 | * @param {number} y
92 | * @return {number}
93 | * @private
94 | */
95 | HierarchicalClusterer.yToLat_ = function(y) {
96 | var merc = Math.PI * (y / 128 - 1);
97 | return (360 / Math.PI) * Math.atan(Math.exp(-merc)) - 90;
98 | };
99 |
100 | /**
101 | * @inheritDoc
102 | */
103 | HierarchicalClusterer.prototype.addItem = function(item) {
104 | var latLng = item.getPosition();
105 | var newPoint = /** @type {KdTreeQueueItem} */ ({
106 | x: HierarchicalClusterer.lngToX_(latLng.lng()),
107 | y: HierarchicalClusterer.latToY_(latLng.lat()),
108 | item: item
109 | });
110 |
111 | this.items_[this.itemCount_] = newPoint;
112 | this.itemCount_++;
113 | };
114 |
115 | /**
116 | * @inheritDoc
117 | */
118 | HierarchicalClusterer.prototype.addItems = function(items) {
119 | for (var i = 0; i < items.length; i++) {
120 | this.addItem(items[i]);
121 | }
122 | };
123 |
124 | /**
125 | * @inheritDoc
126 | */
127 | HierarchicalClusterer.prototype.clearItems = function() {
128 | this.items_ = [];
129 | this.itemCount_ = 0;
130 | };
131 |
132 | /**
133 | * @inheritDoc
134 | */
135 | HierarchicalClusterer.prototype.getItems = function() {
136 | var itemArray = [];
137 | for (var i = 0; i < this.items_.length; i++) {
138 | itemArray[i] = this.items_[i];
139 | }
140 |
141 | return itemArray;
142 | };
143 |
144 | /**
145 | * @inheritDoc
146 | */
147 | HierarchicalClusterer.prototype.removeItem = function(item) {
148 | // item order doesn't matter, so replace removed with last element
149 | // removes first matching item and keeps array dense
150 | for (var i = 0; i < this.itemCount_; i++) {
151 | if (this.items_[i].item === item){
152 | this.itemCount_--;
153 | this.items_[i] = this.items_[this.itemCount_];
154 | this.items_[this.itemCount_] = null;
155 | break;
156 | }
157 | }
158 | };
159 |
160 | /**
161 | * @inheritDoc
162 | */
163 | HierarchicalClusterer.prototype.getClusters = function(zoom) {
164 | var cluster;
165 |
166 | var clusters = [];
167 | for (var i = 0; i < this.itemCount_; i++) {
168 | cluster = new ClusterObject(this.items_[i].x, this.items_[i].x,
169 | this.items_[i].y, this.items_[i].y, [i], clusters.length);
170 | clusters[i] = cluster;
171 | }
172 |
173 | var epsilon = this.clusterDistance_ / (1 << zoom);
174 |
175 | var clusterCount = this.cluster_(clusters, epsilon);
176 |
177 | var finalClusters = [];
178 | for (i = 0; i < clusterCount; i++) {
179 | cluster = clusters[i];
180 | var indices = cluster.indices;
181 | var latLng;
182 | if (indices.length === 1) {
183 | // single point, so just reuse latlng
184 | latLng = this.items_[indices[0]].item.getPosition();
185 | } else {
186 | var lat = HierarchicalClusterer.yToLat_(cluster.y);
187 | var lng = HierarchicalClusterer.xToLng_(cluster.x);
188 | latLng = new google.maps.LatLng(lat, lng);
189 | }
190 |
191 | var items = [];
192 | for (var j = 0; j < cluster.indices.length; j++) {
193 | items[j] = this.items_[cluster.indices[j]].item;
194 | }
195 |
196 | finalClusters[i] = new DefaultCluster(latLng, items);
197 | }
198 |
199 | return finalClusters;
200 | };
201 |
202 | /**
203 | * Cluster the specified objects, using minDistance as a threshold. The
204 | * clustering is done within the clusters array; the returned value is the
205 | * number of the resulting clusters, found in that number of the first slots of
206 | * the clusters array.
207 | * @param {!Array.} clusters The objects to cluster.
208 | * @param {number} minDistance
209 | * @return {number} The number of resulting clusters.
210 | * @private
211 | */
212 | HierarchicalClusterer.prototype.cluster_ = function(clusters, minDistance) {
213 | var kdtree = new KdTree(clusters);
214 | var edgeQueue = this.createShortestEdgeQueue_(clusters, kdtree);
215 |
216 | // merge clusters if within limit
217 | var clusterCount = clusters.length;
218 | var sqEpsilon = minDistance * minDistance;
219 | while (edgeQueue.peek().value < sqEpsilon && clusterCount > 1) {
220 | var shortest = edgeQueue.pop();
221 |
222 | if (shortest.origin.valid) {
223 | var originCluster;
224 | var originClusterIndex;
225 |
226 | if (!shortest.dest.valid) {
227 | // dest not valid, so need to find new dest
228 | originCluster = shortest.origin;
229 | originClusterIndex = originCluster.index;
230 |
231 | } else {
232 | // origin and dest valid, combine into new cluster and find new dest
233 | var item0 = shortest.origin;
234 | var item1 = shortest.dest;
235 |
236 | var indices = item0.indices;
237 | var start = indices.length;
238 | var oldIndices = item1.indices;
239 | var length = oldIndices.length;
240 | for (var i = 0; i < length; i++) {
241 | indices[start + i] = oldIndices[i];
242 | }
243 |
244 | var sumX = item0.sumX + item1.sumX;
245 | var sumY = item0.sumY + item1.sumY;
246 | var newX = sumX / indices.length;
247 | var newY = sumY / indices.length;
248 |
249 | // eliminate old clusters
250 | item0.valid = false;
251 | kdtree.removeItem(item0);
252 | item1.valid = false;
253 | kdtree.removeItem(item1);
254 | originClusterIndex = Math.min(item0.index, item1.index);
255 | var oldIndex = Math.max(item0.index, item1.index);
256 | clusterCount--;
257 | clusters[oldIndex] = clusters[clusterCount];
258 | clusters[oldIndex].index = oldIndex;
259 | clusters[clusterCount] = null;
260 |
261 | originCluster = new ClusterObject(newX, sumX, newY, sumY,
262 | indices, originClusterIndex);
263 |
264 | clusters[originClusterIndex] = originCluster;
265 | kdtree.addItem(originCluster);
266 | }
267 | if (clusterCount < 2) {
268 | break;
269 | }
270 |
271 | // update edge by finding closest cluster dest to new origin
272 | shortest.origin = originCluster;
273 | var newNearest = kdtree.nearestNeighbor(originCluster);
274 | shortest.dest = newNearest.neighbor;
275 | shortest.value = newNearest.distance;
276 |
277 | // put new edge back on queue
278 | edgeQueue.push(shortest);
279 | }
280 | }
281 |
282 | return clusterCount;
283 | };
284 |
285 | /**
286 | * Creates a priority queue of edges, one per cluster, representing the
287 | * distance to the closest other cluster.
288 | * @param {!Array.} clusters
289 | * @param {KdTree} kdtree An acceleration data structure for fast nearest-
290 | * neighbor queries, already populated by clusters array.
291 | * @return {MinPriorityQueue}
292 | * @private
293 | */
294 | HierarchicalClusterer.prototype.createShortestEdgeQueue_ =
295 | function(clusters, kdtree) {
296 | var edges = [];
297 | for (var i = 0; i < clusters.length; i++) {
298 | var cluster = clusters[i];
299 | var nearest = kdtree.nearestNeighbor(cluster);
300 |
301 | var edge = /** @type {ClusterEdge} */ ({
302 | origin: cluster,
303 | dest: nearest.neighbor,
304 | value: nearest.distance
305 | });
306 | edges[i] = edge;
307 | }
308 |
309 | return new MinPriorityQueue(edges);
310 | };
311 |
--------------------------------------------------------------------------------
/src/clustering/kdqueue/KdTreeQueueItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * @interface
19 | */
20 | function KdTreeQueueItem() {}
21 |
22 | /**
23 | * @type {number}
24 | */
25 | KdTreeQueueItem.prototype.x;
26 |
27 | /**
28 | * @type {number}
29 | */
30 | KdTreeQueueItem.prototype.y;
31 |
32 | /**
33 | * @type {ClusterItem}
34 | */
35 | KdTreeQueueItem.prototype.item;
36 |
--------------------------------------------------------------------------------
/src/clustering/lib/MinPriorityQueue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Implementation of a min-priority queue. If initial values are provided via
19 | * opt_elements, the queue will use that array as storage, modifying it in
20 | * place, assuming total ownership afterwards.
21 | * @param {Array.=} opt_elements A set of elements to
22 | * intialize the queue with.
23 | * @constructor
24 | */
25 | function MinPriorityQueue(opt_elements) {
26 | /**
27 | * The heap.
28 | * @private {!Array.}
29 | */
30 | this.elements_ = opt_elements || [];
31 |
32 | /**
33 | * The number of elements in the heap.
34 | * @private {number}
35 | */
36 | this.size_ = this.elements_.length;
37 |
38 | if (opt_elements) {
39 | this.init_();
40 | }
41 | }
42 |
43 | /**
44 | * Ensure that the node at index correctly fulfills heap property with respect
45 | * to its children.
46 | * @param {number} index The index of the node to check
47 | * @private
48 | */
49 | MinPriorityQueue.prototype.heapify_ = function(index) {
50 | var elements = this.elements_;
51 | var minIndex = index;
52 | var item = elements[index];
53 | var itemValue = item.value;
54 | var minItem = item;
55 |
56 | while (true) {
57 | var leftIndex = index * 2 + 1;
58 | if (leftIndex < this.size_) {
59 | var left = elements[leftIndex];
60 | if (left.value < itemValue) {
61 | minIndex = leftIndex;
62 | minItem = left;
63 | }
64 |
65 | var rightIndex = leftIndex + 1;
66 | if (rightIndex < this.size_) {
67 | var right = elements[rightIndex];
68 | if (right.value < minItem.value) {
69 | minIndex = rightIndex;
70 | minItem = right;
71 | }
72 | }
73 |
74 | if (minIndex !== index) {
75 | elements[index] = minItem;
76 | index = minIndex;
77 | } else {
78 | elements[index] = item;
79 | break;
80 | }
81 | } else {
82 | elements[index] = item;
83 | break;
84 | }
85 | }
86 | };
87 |
88 | /**
89 | * Ensure that all nodes fulfill the heap property.
90 | * @private
91 | */
92 | MinPriorityQueue.prototype.init_ = function() {
93 | var last = this.size_ - 1;
94 | var index = Math.floor((last - 1) / 2);
95 |
96 | for (; index >= 0; index--) {
97 | this.heapify_(index);
98 | }
99 | };
100 |
101 | /**
102 | * Returns the current number of elements in the queue.
103 | * @return {number}
104 | */
105 | MinPriorityQueue.prototype.getSize = function() {
106 | return this.size_;
107 | };
108 |
109 | /**
110 | * Returns the minimum element, if any, without removing from the
111 | * queue.
112 | * @return {PriorityQueueNode|undefined}
113 | */
114 | MinPriorityQueue.prototype.peek = function() {
115 | return this.elements_[0];
116 | };
117 |
118 | /**
119 | * Removes the minimum element from the queue, if any, and returns it.
120 | * @return {PriorityQueueNode|undefined}
121 | */
122 | MinPriorityQueue.prototype.pop = function() {
123 | var min = this.elements_[0];
124 | this.size_--;
125 | this.elements_[0] = this.elements_[this.size_];
126 | this.elements_[this.size_] = null;
127 | this.heapify_(0);
128 |
129 | return min;
130 | };
131 |
132 | /**
133 | * Puts the specified element into the queue.
134 | * @param {!PriorityQueueNode} element
135 | */
136 | MinPriorityQueue.prototype.push = function(element) {
137 | var elements = this.elements_;
138 | var index = this.size_;
139 | elements[index] = element;
140 | this.size_++;
141 |
142 | var parentIndex = Math.floor((index - 1) / 2);
143 | while (parentIndex >= 0 && elements[index].value < elements[parentIndex].value) {
144 | var tmp = elements[parentIndex];
145 | elements[parentIndex] = elements[index];
146 | elements[index] = tmp;
147 |
148 | index = parentIndex;
149 | parentIndex = Math.floor((index - 1) / 2);
150 | }
151 | };
152 |
--------------------------------------------------------------------------------
/src/clustering/lib/PriorityQueueNode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * A prioritizable node.
19 | * @interface
20 | *
21 | */
22 | function PriorityQueueNode() {}
23 |
24 | /**
25 | * @type {number}
26 | */
27 | PriorityQueueNode.prototype.value;
28 |
--------------------------------------------------------------------------------
/src/clustering/lib/kdnode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * The required interface for values to be stored in a k-d tree.
19 | * @interface
20 | */
21 | function KdData() {}
22 |
23 | /**
24 | * The x coordinate of the value.
25 | * @type {number}
26 | */
27 | KdData.prototype.x;
28 |
29 | /**
30 | * The y coordinate of the value.
31 | * @type {number}
32 | */
33 | KdData.prototype.y;
34 |
35 | /**
36 | * An index for disambiguating values at the same (x, y) coordinates.
37 | * @type {number}
38 | */
39 | KdData.prototype.index;
40 |
41 | /**
42 | * A node in a k-d tree.
43 | * @param {KdData} data
44 | * @constructor
45 | */
46 | function KdNode(data) {
47 | this.left = null;
48 | this.right = null;
49 | this.data = data;
50 | }
51 |
52 | /**
53 | * An object representing a nearest neighbor.
54 | * @interface
55 | */
56 | function KdNearestNeighbor() {}
57 |
58 | /**
59 | * @type {KdData}
60 | */
61 | KdNearestNeighbor.prototype.neighbor;
62 |
63 | /**
64 | * @type {number}
65 | */
66 | KdNearestNeighbor.prototype.distance;
67 |
--------------------------------------------------------------------------------
/src/clustering/lib/kdtree.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /* global KdNode */
18 |
19 | /**
20 | * A k-d tree data structure. The stored KdData objects are used directly and
21 | * are *not* copied, so any change to their properties will invalidate the
22 | * results produced by this class. The KdData's x and y coordinates are used
23 | * for spatial sorting; its index is used to disambiguate points at the same
24 | * location. If two values in the tree have the same x, y, and index values,
25 | * results may be incorrect and an execution error may occur.
26 | * @param {Array.=} opt_points Initial points for tree. The array
27 | * itself is not retained and may be reused.
28 | * @constructor
29 | */
30 | function KdTree(opt_points) {
31 | var points = opt_points || [];
32 |
33 | var xsorted = [];
34 | var ysorted = [];
35 | for (var i = 0; i < points.length; i++) {
36 | xsorted[i] = points[i];
37 | ysorted[i] = points[i];
38 | }
39 | xsorted.sort(KdTree.compareX_);
40 | ysorted.sort(KdTree.compareY_);
41 |
42 | this.root = KdTree.build_(xsorted, ysorted, true);
43 | }
44 |
45 | /**
46 | * Comparator function for sorting KdData values in x. If two objects have the
47 | * same x value, they are sorted in y, and if equal there, by unique index.
48 | * @param {KdData} a
49 | * @param {KdData} b
50 | * @return {number}
51 | * @private
52 | */
53 | KdTree.compareX_ = function(a, b) {
54 | if (a.x < b.x) {
55 | return -1;
56 | } else if (a.x === b.x) {
57 | if (a.y < b.y) {
58 | return -1;
59 | } else if (a.y === b.y) {
60 | if (a.index < b.index) {
61 | return -1;
62 | } else if (a.index === b.index) {
63 | return 0;
64 | }
65 | }
66 | }
67 | return 1;
68 | };
69 |
70 | /**
71 | * Returns true if a is strictly less than b in x, otherwise false. See
72 | * KdTree.compareX_ for ordering details.
73 | * @param {KdData} a
74 | * @param {KdData} b
75 | * @return {boolean}
76 | * @private
77 | */
78 | KdTree.lessThanX_ = function(a, b) {
79 | return (a.x < b.x) || (a.x === b.x && ((a.y < b.y) || (a.y === b.y && a.index < b.index)));
80 | };
81 |
82 | /**
83 | * Comparator function for sorting KdData values in y. If two objects have the
84 | * same y value, they are sorted in x, and if equal there, by unique index.
85 | * @param {KdData} a
86 | * @param {KdData} b
87 | * @return {number}
88 | * @private
89 | */
90 | KdTree.compareY_ = function(a, b) {
91 | if (a.y < b.y) {
92 | return -1;
93 | } else if (a.y === b.y) {
94 | if (a.x < b.x) {
95 | return -1;
96 | } else if (a.x === b.x) {
97 | if (a.index < b.index) {
98 | return -1;
99 | } else if (a.index === b.index) {
100 | return 0;
101 | }
102 | }
103 | }
104 | return 1;
105 | };
106 |
107 | /**
108 | * Returns true if a is strictly less than b in y, otherwise false. See
109 | * KdTree.compareY_ for ordering details.
110 | * @param {KdData} a
111 | * @param {KdData} b
112 | * @return {boolean}
113 | * @private
114 | */
115 | KdTree.lessThanY_ = function(a, b) {
116 | return (a.y < b.y) || (a.y === b.y && ((a.x < b.x) || (a.x === b.x && a.index < b.index)));
117 | };
118 |
119 | /**
120 | * Build a k-d tree from the provided values.
121 | * @param {!Array.} sorted0 An array of values to put in the tree,
122 | * sorted in the dimension indeicated by xLevel.
123 | * @param {!Array.} sorted1 An array of the same values, sorted in the
124 | * other dimension.
125 | * @param {boolean} xLevel True if x is the dimension to sort in at this level,
126 | * false if y.
127 | * @return {KdNode} The root of the tree created from values.
128 | * @private
129 | */
130 | KdTree.build_ = function(sorted0, sorted1, xLevel) {
131 | var node;
132 |
133 | // base cases
134 | if (sorted0.length === 1) {
135 | node = new KdNode(sorted0[0]);
136 | return node;
137 | } else if (sorted0.length === 0) {
138 | return null;
139 | }
140 |
141 | var medianIndex = (sorted0.length / 2) | 0;
142 | var median = sorted0[medianIndex];
143 | node = new KdNode(median);
144 |
145 | var left0 = [];
146 | var right0 = [];
147 | for (var i = 0; i < medianIndex; i++) {
148 | left0[i] = sorted0[i];
149 | }
150 | i++;
151 | for (var j = 0; i < sorted0.length; i++) {
152 | right0[j++] = sorted0[i];
153 | }
154 |
155 | var left1 = [];
156 | var right1 = [];
157 | j = 0;
158 | var k = 0;
159 | for (i = 0; i < sorted1.length; i++) {
160 | var point = sorted1[i];
161 | if (point === median) {
162 | continue;
163 | }
164 |
165 | if (xLevel) {
166 | if (KdTree.lessThanX_(point, median)) {
167 | left1[j++] = point;
168 | } else {
169 | right1[k++] = point;
170 | }
171 | } else {
172 | if (KdTree.lessThanY_(point, median)) {
173 | left1[j++] = point;
174 | } else {
175 | right1[k++] = point;
176 | }
177 | }
178 | }
179 |
180 | node.left = KdTree.build_(left1, left0, !xLevel);
181 | node.right = KdTree.build_(right1, right0, !xLevel);
182 |
183 | return node;
184 | };
185 |
186 | /**
187 | * Find the minimum value in the specified dimension.
188 | * @param {KdNode} node The root node of the search.
189 | * @param {boolean} inX If finding the minimum in x, true, in y, false.
190 | * @param {boolean} xLevel If node's children are sorted in x, true, in y,
191 | * false.
192 | * @return {KdData}
193 | * @private
194 | */
195 | KdTree.findMinimum_ = function(node, inX, xLevel) {
196 | if (node === null) {
197 | return null;
198 | }
199 |
200 | if (inX === xLevel) {
201 | if (!node.left) {
202 | return node.data;
203 | } else {
204 | return KdTree.findMinimum_(node.left, inX, !xLevel);
205 | }
206 |
207 | } else {
208 | // return minimum of left, right, and node
209 | var left = KdTree.findMinimum_(node.left, inX, !xLevel);
210 | var right = KdTree.findMinimum_(node.right, inX, !xLevel);
211 | var minimum = node.data;
212 |
213 | if (inX) {
214 | if (left && KdTree.lessThanX_(left, minimum)) {
215 | minimum = left;
216 | }
217 | if (right && KdTree.lessThanX_(right, minimum)) {
218 | minimum = right;
219 | }
220 | } else {
221 | if (left && KdTree.lessThanY_(left, minimum)) {
222 | minimum = left;
223 | }
224 | if (right && KdTree.lessThanY_(right, minimum)) {
225 | minimum = right;
226 | }
227 | }
228 |
229 | return minimum;
230 | }
231 | };
232 |
233 | /**
234 | * Find the maximum value in the specified dimension.
235 | * @param {KdNode} node The root node of the search.
236 | * @param {boolean} inX If finding the maximum in x, true, in y, false.
237 | * @param {boolean} xLevel If node's children are sorted in x, true, in y,
238 | * false.
239 | * @return {KdData}
240 | * @private
241 | */
242 | KdTree.findMaximum_ = function(node, inX, xLevel) {
243 | if (node === null) {
244 | return null;
245 | }
246 |
247 | if (inX === xLevel) {
248 | if (!node.right) {
249 | return node.data;
250 | } else {
251 | return KdTree.findMaximum_(node.right, inX, !xLevel);
252 | }
253 |
254 | } else {
255 | // return maximum of left, right, and node
256 | var left = KdTree.findMaximum_(node.left, inX, !xLevel);
257 | var right = KdTree.findMaximum_(node.right, inX, !xLevel);
258 | var maximum = node.data;
259 |
260 | // NOTE: left !== right !== node.data, so !lessThan works for greaterThan
261 | if (inX) {
262 | if (left && !KdTree.lessThanX_(left, maximum)) {
263 | maximum = left;
264 | }
265 | if (right && !KdTree.lessThanX_(right, maximum)) {
266 | maximum = right;
267 | }
268 | } else {
269 | if (left && !KdTree.lessThanY_(left, maximum)) {
270 | maximum = left;
271 | }
272 | if (right && !KdTree.lessThanY_(right, maximum)) {
273 | maximum = right;
274 | }
275 | }
276 |
277 | return maximum;
278 | }
279 | };
280 |
281 | /**
282 | * Search for a value in the tree starting at specified root, removing it if
283 | * found, and returning the root (the new one, if changed).
284 | * @param {KdData} item The value to remove.
285 | * @param {KdNode} root The root node of the search for item.
286 | * @param {boolean} xLevel If root's children are sorted in x, true, in y,
287 | * false.
288 | * @return {KdNode}
289 | * @private
290 | */
291 | KdTree.delete_ = function(item, root, xLevel) {
292 | // TODO(bckenny): currently ~10% of execution time (including findMin/Max)
293 | if (root === null) {
294 | // TODO(bckenny): not sure what to do in this case yet
295 |
296 | } else if (root.data === item) {
297 | if (root.right !== null) {
298 | // take replacement from minimum on right
299 | root.data = KdTree.findMinimum_(root.right, xLevel, !xLevel);
300 | root.right = KdTree.delete_(root.data, root.right, !xLevel);
301 | } else if (root.left !== null) {
302 | root.data = KdTree.findMaximum_(root.left, xLevel, !xLevel);
303 | root.left = KdTree.delete_(root.data, root.left, !xLevel);
304 | } else {
305 | // leaf, so just delete
306 | root.data = null;
307 | return null;
308 | }
309 |
310 | } else {
311 | // can't be equal, so use lessThan/greaterThan
312 | var lessThan = xLevel ? KdTree.lessThanX_(item, root.data) : KdTree.lessThanY_(item, root.data);
313 | if (lessThan) {
314 | root.left = KdTree.delete_(item, root.left, !xLevel);
315 | } else {
316 | root.right = KdTree.delete_(item, root.right, !xLevel);
317 | }
318 | }
319 |
320 | return root;
321 | };
322 |
323 | /**
324 | * Find the nearest value in the tree to target, as defined by comparator
325 | * sorting.
326 | * @param {KdData} target
327 | * @param {KdNode} root The root node of the search for a neighbor.
328 | * @param {boolean} xLevel If root's children are sorted in x, true, in y,
329 | * false.
330 | * @param {KdNearestNeighbor} candidate The closest neighbor so far.
331 | * @return {KdNearestNeighbor}
332 | * @private
333 | */
334 | KdTree.nearest_ = function(target, root, xLevel, candidate) {
335 | if (root === null) {
336 | return candidate;
337 | }
338 |
339 | var data = root.data;
340 | var xDiff = target.x - data.x;
341 | var yDiff = target.y - data.y;
342 | xDiff *= xDiff;
343 | yDiff *= yDiff;
344 |
345 | // test this node, but skip over if self
346 | if (target !== data) {
347 | var distance = xDiff + yDiff;
348 | if (distance < candidate.distance) {
349 | candidate.distance = distance;
350 | candidate.neighbor = data;
351 | }
352 | }
353 |
354 | // recurse to children, prioritizing the nearer half
355 | var lessThan;
356 | var testDistance;
357 | if (xLevel) {
358 | lessThan = target.x < data.x;
359 | testDistance = xDiff;
360 | } else {
361 | lessThan = target.y < data.y;
362 | testDistance = yDiff;
363 | }
364 |
365 | if (lessThan) {
366 | candidate = KdTree.nearest_(target, root.left, !xLevel, candidate);
367 | if (root.right && testDistance <= candidate.distance) {
368 | candidate = KdTree.nearest_(target, root.right, !xLevel, candidate);
369 | }
370 |
371 | } else {
372 | candidate = KdTree.nearest_(target, root.right, !xLevel, candidate);
373 | if (root.left && testDistance <= candidate.distance) {
374 | candidate = KdTree.nearest_(target, root.left, !xLevel, candidate);
375 | }
376 | }
377 |
378 | return candidate;
379 | };
380 |
381 | /**
382 | * Add a value to the k-d tree.
383 | * @param {KdData} item
384 | */
385 | KdTree.prototype.addItem = function(item) {
386 | if (!this.root) {
387 | this.root = new KdNode(item);
388 | return;
389 | }
390 |
391 | var node = this.root;
392 | var xLevel = true;
393 |
394 | while (true) {
395 | var lessThan = xLevel ? KdTree.lessThanX_(item, node.data) : KdTree.lessThanY_(item, node.data);
396 |
397 | if (item === node.data) {
398 | throw new Error('duplicate entry by our metric');
399 |
400 | } else if (lessThan) {
401 | if (!node.left) {
402 | node.left = new KdNode(item);
403 | return;
404 | }
405 | node = node.left;
406 | xLevel = !xLevel;
407 |
408 | } else {
409 | if (!node.right) {
410 | node.right = new KdNode(item);
411 | return;
412 | }
413 | node = node.right;
414 | xLevel = !xLevel;
415 | }
416 | }
417 | };
418 |
419 | /**
420 | * Remove an item from the k-d tree, if present.
421 | * @param {KdData} item
422 | */
423 | KdTree.prototype.removeItem = function(item) {
424 | this.root = KdTree.delete_(item, this.root, true);
425 | };
426 |
427 | /**
428 | * Find the nearest neighbor to item in the tree.
429 | * @param {KdData} item
430 | * @return {KdNearestNeighbor}
431 | */
432 | KdTree.prototype.nearestNeighbor = function(item) {
433 | var candidate = /** @type {KdNearestNeighbor} */ ({
434 | neighbor: null,
435 | distance: Number.MAX_VALUE
436 | });
437 |
438 | return KdTree.nearest_(item, this.root, true, candidate);
439 | };
440 |
--------------------------------------------------------------------------------