├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── conversantmedia │ │ └── util │ │ └── collection │ │ ├── geometry │ │ ├── Point1d.java │ │ ├── Point2d.java │ │ ├── Point3d.java │ │ ├── Range1d.java │ │ ├── Rect2d.java │ │ └── Rect3d.java │ │ └── spatial │ │ ├── AxialSplitLeaf.java │ │ ├── Branch.java │ │ ├── ConcurrentRTree.java │ │ ├── CounterNode.java │ │ ├── HyperPoint.java │ │ ├── HyperRect.java │ │ ├── Leaf.java │ │ ├── LinearSplitLeaf.java │ │ ├── Node.java │ │ ├── QuadraticSplitLeaf.java │ │ ├── RTree.java │ │ ├── RectBuilder.java │ │ ├── SpatialSearch.java │ │ ├── SpatialSearches.java │ │ └── Stats.java └── resources │ ├── RTree.png │ └── mM.png └── test └── java └── com └── conversantmedia └── util └── collection └── spatial ├── AxialSplitLeafTest.java ├── ConcurrentRTreeTest.java ├── LinearSplitLeafTest.java ├── QuadraticSplitLeafTest.java ├── RTreeTest.java ├── Rect1DTest.java ├── Rect2DTest.java └── Rect3DTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Maven # 2 | ######### 3 | target/ 4 | 5 | # Intellij # 6 | ############ 7 | *.iml 8 | *.iws 9 | .idea/ 10 | 11 | # Eclipse # 12 | ########### 13 | .classpath 14 | .project 15 | .settings/ 16 | 17 | # Netbeans # 18 | ############ 19 | nbactions.xml 20 | nb-configuration.xml 21 | 22 | # Python # 23 | ########## 24 | env 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Conversant RTree 4 | 5 | RTree is an index that supports building a tree of bounding rectangles for arbitrary range searches. RTrees are efficient for geospatial data but can be extended to support any data that is amenable to range searching. 6 | 7 | Conversant RTree is a hyper-dimensional (2D, 3D, 4D, nD) implementation of RTrees in Java. Conversant RTree supports data with large numbers of orthogonal relations or high dimensionality in the same way that traditional RTrees support 2 or 3 dimensional spatial data. 8 | 9 | ### Conversant R-Tree is on Maven Central 10 | 11 | Maven users can incorporate Conversant R-Tree the usual way. 12 | 13 | ``` 14 | 15 | com.conversantmedia 16 | rtree 17 | 1.0.5 18 | 19 | ``` 20 | 21 | Optionally specify a classifier for your version of Java 22 | 23 | | classifier | 24 | |:------------:| 25 | | jdk8 | 26 | | jdk10 | 27 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.conversantmedia 5 | rtree 6 | jar 7 | 1.0.6-SNAPSHOT 8 | ${project.groupId}:${project.artifactId} 9 | https://github.com/conversant/rtree 10 | Conversant RTree - N dimensional spatial index 11 | 2015 12 | 13 | 14 | Conversant Engineering 15 | http://engineering.conversantmedia.com 16 | 17 | 18 | 19 | 20 | John Cairns 21 | john@2ad.com 22 | Conversant, Inc. 23 | http://engineering.conversantmedia.com 24 | 25 | 26 | 27 | Jim Covert 28 | covert.james@gmail.com 29 | Conversant, Inc. 30 | http://engineering.conversantmedia.com 31 | 32 | 33 | 34 | Evan White 35 | ewhite1@conversantmedia.com 36 | Conversant, Inc. 37 | http://engineering.conversantmedia.com 38 | 39 | 40 | 41 | 42 | 43 | The Apache License, Version 2.0 44 | http://www.apache.org/licenses/LICENSE-2.0.txt 45 | 46 | 47 | 48 | 49 | scm:git:https://github.com/conversant/rtree.git 50 | scm:git:https://github.com/conversant/rtree.git 51 | https://github.com/conversant/rtree 52 | HEAD 53 | 54 | 55 | 56 | 57 | ${snapshots.repo.id} 58 | ${snapshots.repo.url} 59 | 60 | 61 | ossrh 62 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 63 | 64 | 65 | 66 | 67 | GitHub 68 | https://github.com/conversant/rtree/issues 69 | 70 | 71 | 72 | 3.0.2 73 | 74 | 75 | 76 | 1.8 77 | UTF-8 78 | UTF-8 79 | ossrh 80 | https://oss.sonatype.org/content/repositories/snapshots 81 | apache_v2 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-compiler-plugin 89 | 3.1 90 | true 91 | 92 | ${jdk.version} 93 | ${jdk.version} 94 | ${project.build.sourceEncoding} 95 | 96 | 97 | 98 | 99 | org.codehaus.mojo 100 | license-maven-plugin 101 | 1.8 102 | 103 | ${license.name} 104 | ~~ 105 | false 106 | false 107 | 108 | 109 | 110 | maven-surefire-plugin 111 | 2.17 112 | 113 | 114 | maven-failsafe-plugin 115 | 2.17 116 | 117 | 118 | integration-test 119 | 120 | integration-test 121 | 122 | 123 | 124 | verify 125 | 126 | verify 127 | 128 | 129 | 130 | 131 | 132 | maven-surefire-report-plugin 133 | 2.17 134 | 135 | 136 | maven-source-plugin 137 | 2.2.1 138 | 139 | 140 | attach-sources 141 | 142 | jar-no-fork 143 | 144 | 145 | 146 | 147 | 148 | maven-javadoc-plugin 149 | 2.9.1 150 | 151 | 152 | attach-javadocs 153 | 154 | jar 155 | 156 | 157 | 158 | 159 | 160 | maven-jar-plugin 161 | 162 | ${jdkClassifier} 163 | 164 | 165 | 166 | maven-resources-plugin 167 | 2.6 168 | 169 | 170 | maven-assembly-plugin 171 | 2.4 172 | 173 | 174 | maven-release-plugin 175 | 2.5 176 | 177 | @{project.version} 178 | 179 | 180 | 181 | org.codehaus.mojo 182 | cobertura-maven-plugin 183 | 2.6 184 | 185 | 1024m 186 | 187 | 188 | 189 | org.codehaus.mojo 190 | build-helper-maven-plugin 191 | 1.8 192 | 193 | 194 | maven-site-plugin 195 | 3.3 196 | 197 | 198 | org.sonatype.plugins 199 | nexus-staging-maven-plugin 200 | 1.6.3 201 | true 202 | 203 | ossrh 204 | https://oss.sonatype.org/ 205 | true 206 | 207 | 208 | 209 | org.apache.maven.plugins 210 | maven-gpg-plugin 211 | 1.5 212 | 213 | 214 | sign-artifacts 215 | verify 216 | 217 | sign 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | jdk8 228 | 229 | 1.8 230 | 231 | 232 | jdk8 233 | 234 | 235 | 236 | jdk10 237 | 238 | 10 239 | 240 | 241 | jdk10 242 | 243 | 244 | 245 | 246 | 247 | 248 | junit 249 | junit 250 | 4.12 251 | test 252 | 253 | 254 | org.mockito 255 | mockito-all 256 | 1.10.19 257 | test 258 | 259 | 260 | 261 | 262 | 263 | 264 | org.codehaus.mojo 265 | cobertura-maven-plugin 266 | 2.6 267 | 268 | 269 | 270 | 271 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/geometry/Point1d.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.geometry; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2018, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * John Cairns © 2018 9 | * ~~ 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | * #L% 22 | */ 23 | 24 | import com.conversantmedia.util.collection.spatial.HyperPoint; 25 | import com.conversantmedia.util.collection.spatial.HyperRect; 26 | import com.conversantmedia.util.collection.spatial.RTree; 27 | import com.conversantmedia.util.collection.spatial.RectBuilder; 28 | 29 | public final class Point1d implements HyperPoint { 30 | 31 | final double x; 32 | 33 | public Point1d(final double x) { 34 | this.x = x; 35 | } 36 | 37 | @Override 38 | public int getNDim() { 39 | return 1; 40 | } 41 | 42 | @Override 43 | public Double getCoord(final int d) { 44 | if(d==0) { 45 | return x; 46 | } else { 47 | throw new IllegalArgumentException("Invalid dimension"); 48 | } 49 | } 50 | 51 | @Override 52 | public double distance(final HyperPoint p) { 53 | final Point1d p2 = (Point1d)p; 54 | 55 | final double dx = p2.x - x; 56 | return dx; 57 | } 58 | 59 | @Override 60 | public double distance(final HyperPoint p, final int d) { 61 | final Point1d p2 = (Point1d)p; 62 | if(d == 0) { 63 | return distance(p); 64 | } else { 65 | throw new IllegalArgumentException("Invalid dimension"); 66 | } 67 | } 68 | 69 | public boolean equals(final Object o) { 70 | if(this == o) return true; 71 | if(o==null || getClass() != o.getClass()) return false; 72 | 73 | final Point1d p = (Point1d) o; 74 | return RTree.isEqual(x, p.x); 75 | } 76 | 77 | 78 | public int hashCode() { 79 | return Double.hashCode(x); 80 | } 81 | 82 | public final static class Builder implements RectBuilder { 83 | 84 | @Override 85 | public HyperRect getBBox(final Point1d point) { 86 | return new Range1d(point, point); 87 | } 88 | 89 | @Override 90 | public HyperRect getMbr(final HyperPoint p1, final HyperPoint p2) { 91 | final Point1d point1 = (Point1d)p1; 92 | final Point1d point2 = (Point1d)p2; 93 | return new Range1d(point1, point2); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/geometry/Point2d.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.geometry; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.spatial.HyperPoint; 24 | import com.conversantmedia.util.collection.spatial.HyperRect; 25 | import com.conversantmedia.util.collection.spatial.RTree; 26 | import com.conversantmedia.util.collection.spatial.RectBuilder; 27 | 28 | /** 29 | * Created by jcovert on 6/15/15. 30 | */ 31 | public final class Point2d implements HyperPoint { 32 | public static final int X = 0; 33 | public static final int Y = 1; 34 | 35 | final double x, y; 36 | 37 | public Point2d(final double x, final double y) { 38 | this.x = x; 39 | this.y = y; 40 | } 41 | 42 | @Override 43 | public int getNDim() { 44 | return 2; 45 | } 46 | 47 | @Override 48 | public Double getCoord(final int d) { 49 | if(d==X) { 50 | return x; 51 | } else if(d==Y) { 52 | return y; 53 | } else { 54 | throw new IllegalArgumentException("Invalid dimension"); 55 | } 56 | } 57 | 58 | @Override 59 | public double distance(final HyperPoint p) { 60 | final Point2d p2 = (Point2d)p; 61 | 62 | final double dx = p2.x-x; 63 | final double dy = p2.y-y; 64 | return Math.sqrt(dx*dx + dy*dy); 65 | } 66 | 67 | @Override 68 | public double distance(final HyperPoint p, final int d) { 69 | final Point2d p2 = (Point2d)p; 70 | if(d == X) { 71 | return Math.abs(p2.x - x); 72 | } else if (d == Y) { 73 | return Math.abs(p2.y - y); 74 | } else { 75 | throw new IllegalArgumentException("Invalid dimension"); 76 | } 77 | } 78 | 79 | public boolean equals(final Object o) { 80 | if(this == o) return true; 81 | if(o==null || getClass() != o.getClass()) return false; 82 | 83 | final Point2d p = (Point2d) o; 84 | return RTree.isEqual(x, p.x) && 85 | RTree.isEqual(y, p.y); 86 | } 87 | 88 | 89 | public int hashCode() { 90 | return Double.hashCode(x) ^ 31*Double.hashCode(y); 91 | } 92 | 93 | public final static class Builder implements RectBuilder { 94 | 95 | @Override 96 | public HyperRect getBBox(final Point2d point) { 97 | return new Rect2d(point); 98 | } 99 | 100 | @Override 101 | public HyperRect getMbr(final HyperPoint p1, final HyperPoint p2) { 102 | final Point2d point1 = (Point2d)p1; 103 | final Point2d point2 = (Point2d)p2; 104 | return new Rect2d(point1, point2); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/geometry/Point3d.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.geometry; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.spatial.HyperPoint; 24 | import com.conversantmedia.util.collection.spatial.HyperRect; 25 | import com.conversantmedia.util.collection.spatial.RTree; 26 | import com.conversantmedia.util.collection.spatial.RectBuilder; 27 | 28 | public final class Point3d implements HyperPoint { 29 | public final static int X = 0; 30 | public final static int Y = 1; 31 | public final static int Z = 2; 32 | 33 | 34 | final double x, y, z; 35 | 36 | public Point3d(final double x, final double y, final double z) { 37 | this.x = x; 38 | this.y = y; 39 | this.z = z; 40 | } 41 | 42 | @Override 43 | public int getNDim() { 44 | return 3; 45 | } 46 | 47 | @Override 48 | public Double getCoord(final int d) { 49 | if(d==X) { 50 | return x; 51 | } else if(d==Y) { 52 | return y; 53 | } else if(d==Z) { 54 | return z; 55 | } else { 56 | throw new IllegalArgumentException("Invalid dimension"); 57 | } 58 | } 59 | 60 | @Override 61 | public double distance(final HyperPoint p) { 62 | final Point3d p2 = (Point3d)p; 63 | 64 | final double dx = p2.x-x; 65 | final double dy = p2.y-y; 66 | final double dz = p2.z-z; 67 | return Math.sqrt(dx*dx + dy*dy + dz*dz); 68 | } 69 | 70 | @Override 71 | public double distance(final HyperPoint p, final int d) { 72 | final Point3d p2 = (Point3d)p; 73 | if(d == X) { 74 | return Math.abs(p2.x - x); 75 | } else if (d == Y) { 76 | return Math.abs(p2.y - y); 77 | } else if (d == Z) { 78 | return Math.abs(p2.z - z); 79 | } else { 80 | throw new IllegalArgumentException("Invalid dimension"); 81 | } 82 | } 83 | 84 | @Override 85 | public boolean equals(Object o) { 86 | if (this == o) return true; 87 | if (o == null || getClass() != o.getClass()) return false; 88 | 89 | final Point3d p = (Point3d)o; 90 | return RTree.isEqual(x, p.x) && 91 | RTree.isEqual(y, p.y) && 92 | RTree.isEqual(z, p.z); 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | return Double.hashCode(x) ^ 98 | 31*Double.hashCode(y) ^ 99 | 31*31*Double.hashCode(z); 100 | } 101 | 102 | public final static class Builder implements RectBuilder { 103 | 104 | @Override 105 | public HyperRect getBBox(final Point3d point) { 106 | return new Rect3d(point); 107 | } 108 | 109 | @Override 110 | public HyperRect getMbr(final HyperPoint p1, final HyperPoint p2) { 111 | return new Rect3d((Point3d)p1, (Point3d)p2); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/geometry/Range1d.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.geometry; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2018, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * John Cairns © 2018 9 | * ~~ 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | * #L% 22 | */ 23 | 24 | import com.conversantmedia.util.collection.spatial.HyperPoint; 25 | import com.conversantmedia.util.collection.spatial.HyperRect; 26 | import com.conversantmedia.util.collection.spatial.RectBuilder; 27 | 28 | 29 | // 1D rectangle for range searching 30 | public final class Range1d implements HyperRect { 31 | final Point1d min, max; 32 | 33 | public Range1d(final Point1d p1, final Point1d p2) { 34 | if(p1.x < p2.x) { 35 | this.min = new Point1d(p1.x); 36 | this.max = new Point1d(p2.x); 37 | } else { 38 | this.min = new Point1d(p2.x); 39 | this.max = new Point1d(p1.x); 40 | } 41 | } 42 | 43 | 44 | public Range1d(final double x1, final double x2) { 45 | if(x1 < x2) { 46 | min = new Point1d(x1); 47 | max = new Point1d(x2); 48 | } else { 49 | min = new Point1d(x2); 50 | max = new Point1d(x1); 51 | } 52 | } 53 | 54 | @Override 55 | public HyperRect getMbr(final HyperRect r) { 56 | final Range1d r2 = (Range1d)r; 57 | final double minT = Math.min(min.x, r2.min.x); 58 | final double maxT = Math.max(max.x, r2.max.x); 59 | 60 | return new Range1d(minT, maxT); 61 | 62 | } 63 | 64 | @Override 65 | public int getNDim() { 66 | return 1; 67 | } 68 | 69 | @Override 70 | public HyperPoint getCentroid() { 71 | final double dx = min.x + (max.x - min.x)/2.0; 72 | 73 | return new Point1d(dx); 74 | } 75 | 76 | @Override 77 | public HyperPoint getMin() { 78 | return min; 79 | } 80 | 81 | @Override 82 | public HyperPoint getMax() { 83 | return max; 84 | } 85 | 86 | @Override 87 | public double getRange(final int d) { 88 | if(d == 0) { 89 | return max.x - min.x; 90 | } else { 91 | throw new IllegalArgumentException("Invalid dimension"); 92 | } 93 | } 94 | 95 | @Override 96 | public boolean contains(final HyperRect r) { 97 | final Range1d r2 = (Range1d)r; 98 | 99 | return min.x <= r2.min.x && 100 | max.x >= r2.max.x; 101 | } 102 | 103 | @Override 104 | public boolean intersects(final HyperRect r) { 105 | final Range1d r2 = (Range1d)r; 106 | 107 | if(min.x > r2.max.x || 108 | r2.min.x > max.x) { 109 | return false; 110 | } 111 | 112 | return true; 113 | } 114 | 115 | @Override 116 | public double cost() { 117 | final double dx = max.x - min.x; 118 | return Math.abs(dx); 119 | } 120 | 121 | @Override 122 | public double perimeter() { 123 | double p = 0.0; 124 | final int nD = this.getNDim(); 125 | for(int d = 0; d { 161 | 162 | @Override 163 | public HyperRect getBBox(final Range1d rect2D) { 164 | return rect2D; 165 | } 166 | 167 | @Override 168 | public HyperRect getMbr(final HyperPoint p1, final HyperPoint p2) { 169 | return new Range1d(p1.getCoord(0), p2.getCoord(0)); 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/geometry/Rect2d.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.geometry; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.spatial.HyperPoint; 24 | import com.conversantmedia.util.collection.spatial.HyperRect; 25 | import com.conversantmedia.util.collection.spatial.RectBuilder; 26 | 27 | public final class Rect2d implements HyperRect { 28 | final Point2d min, max; 29 | 30 | public Rect2d(final Point2d p) { 31 | min = new Point2d(p.x, p.y); 32 | max = new Point2d(p.x, p.y); 33 | } 34 | 35 | public Rect2d(final double x1, final double y1, final double x2, final double y2) { 36 | min = new Point2d(x1, y1); 37 | max = new Point2d(x2, y2); 38 | } 39 | 40 | public Rect2d(final Point2d p1, final Point2d p2) { 41 | final double minX, minY, maxX, maxY; 42 | 43 | if(p1.x < p2.x) { 44 | minX = p1.x; 45 | maxX = p2.x; 46 | } else { 47 | minX = p2.x; 48 | maxX = p2.x; 49 | } 50 | 51 | if(p1.y < p2.y) { 52 | minY = p1.y; 53 | maxY = p2.y; 54 | } else { 55 | minY = p2.y; 56 | maxY = p2.y; 57 | } 58 | 59 | min = new Point2d(minX, minY); 60 | max = new Point2d(maxX, maxY); 61 | } 62 | 63 | @Override 64 | public HyperRect getMbr(final HyperRect r) { 65 | final Rect2d r2 = (Rect2d)r; 66 | final double minX = Math.min(min.x, r2.min.x); 67 | final double minY = Math.min(min.y, r2.min.y); 68 | final double maxX = Math.max(max.x, r2.max.x); 69 | final double maxY = Math.max(max.y, r2.max.y); 70 | 71 | return new Rect2d(minX, minY, maxX, maxY); 72 | 73 | } 74 | 75 | @Override 76 | public int getNDim() { 77 | return 2; 78 | } 79 | 80 | @Override 81 | public HyperPoint getCentroid() { 82 | final double dx = min.x + (max.x - min.x)/2.0; 83 | final double dy = min.y + (max.y - min.y)/2.0; 84 | 85 | return new Point2d(dx, dy); 86 | } 87 | 88 | @Override 89 | public HyperPoint getMin() { 90 | return min; 91 | } 92 | 93 | @Override 94 | public HyperPoint getMax() { 95 | return max; 96 | } 97 | 98 | @Override 99 | public double getRange(final int d) { 100 | if(d == 0) { 101 | return max.x - min.x; 102 | } else if(d == 1) { 103 | return max.y - min.y; 104 | } else { 105 | throw new IllegalArgumentException("Invalid dimension"); 106 | } 107 | } 108 | 109 | @Override 110 | public boolean contains(final HyperRect r) { 111 | final Rect2d r2 = (Rect2d)r; 112 | 113 | return min.x <= r2.min.x && 114 | max.x >= r2.max.x && 115 | min.y <= r2.min.y && 116 | max.y >= r2.max.y; 117 | } 118 | 119 | @Override 120 | public boolean intersects(final HyperRect r) { 121 | final Rect2d r2 = (Rect2d)r; 122 | 123 | if(min.x > r2.max.x || 124 | r2.min.x > max.x || 125 | min.y > r2.max.y || 126 | r2.min.y > max.y) { 127 | return false; 128 | } 129 | 130 | return true; 131 | } 132 | 133 | @Override 134 | public double cost() { 135 | final double dx = max.x - min.x; 136 | final double dy = max.y - min.y; 137 | return Math.abs(dx*dy); 138 | } 139 | 140 | @Override 141 | public double perimeter() { 142 | double p = 0.0; 143 | final int nD = this.getNDim(); 144 | for(int d = 0; d { 184 | 185 | @Override 186 | public HyperRect getBBox(final Rect2d rect2D) { 187 | return rect2D; 188 | } 189 | 190 | @Override 191 | public HyperRect getMbr(final HyperPoint p1, final HyperPoint p2) { 192 | return new Rect2d(p1.getCoord(0), p1.getCoord(1), p2.getCoord(0), p2.getCoord(1)); 193 | } 194 | } 195 | } -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/geometry/Rect3d.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.geometry; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.spatial.HyperPoint; 24 | import com.conversantmedia.util.collection.spatial.HyperRect; 25 | import com.conversantmedia.util.collection.spatial.RectBuilder; 26 | 27 | public final class Rect3d implements HyperRect { 28 | private final Point3d min, max; 29 | 30 | Rect3d(final Point3d p) { 31 | min = new Point3d(p.x, p.y, p.z); 32 | max = new Point3d(p.x, p.y, p.z); 33 | } 34 | 35 | public Rect3d(final double x1, final double y1, final double z1, final double x2, final double y2, final double z2) { 36 | min = new Point3d(x1, y1, z1); 37 | max = new Point3d(x2, y2, z2); 38 | } 39 | 40 | Rect3d(final Point3d point1, final Point3d point2) { 41 | 42 | final double minx, maxx, miny, maxy, minz, maxz; 43 | 44 | if(point1.x < point2.x) { 45 | minx = point1.x; 46 | maxx = point2.x; 47 | } else { 48 | minx = point2.x; 49 | maxx = point1.x; 50 | } 51 | 52 | if(point1.y < point2.y) { 53 | miny = point1.y; 54 | maxy = point2.y; 55 | } else { 56 | miny = point2.y; 57 | maxy = point1.y; 58 | } 59 | 60 | if(point1.z < point2.z) { 61 | minz = point1.z; 62 | maxz = point2.z; 63 | } else { 64 | minz = point2.z; 65 | maxz = point1.z; 66 | } 67 | 68 | min = new Point3d(minx, miny, minz); 69 | max = new Point3d(maxx, maxy, maxz); 70 | } 71 | 72 | @Override 73 | public HyperRect getMbr(final HyperRect r) { 74 | final Rect3d r2 = (Rect3d)r; 75 | final double minX = Math.min(min.x, r2.min.x); 76 | final double minY = Math.min(min.y, r2.min.y); 77 | final double minZ = Math.min(min.z, r2.min.z); 78 | final double maxX = Math.max(max.x, r2.max.x); 79 | final double maxY = Math.max(max.y, r2.max.y); 80 | final double maxZ = Math.max(max.z, r2.max.z); 81 | 82 | return new Rect3d(minX, minY, minZ, maxX, maxY, maxZ); 83 | 84 | } 85 | 86 | @Override 87 | public int getNDim() { 88 | return 3; 89 | } 90 | 91 | @Override 92 | public HyperPoint getCentroid() { 93 | final double dx = min.x + (max.x - min.x)/2.0; 94 | final double dy = min.y + (max.y - min.y)/2.0; 95 | final double dz = min.z + (max.z - min.z)/2.0; 96 | 97 | return new Point3d(dx, dy, dz); 98 | } 99 | 100 | @Override 101 | public HyperPoint getMin() { 102 | return min; 103 | } 104 | 105 | @Override 106 | public HyperPoint getMax() { 107 | return max; 108 | } 109 | 110 | @Override 111 | public double getRange(final int d) { 112 | if(d == 0) { 113 | return max.x - min.x; 114 | } else if(d == 1) { 115 | return max.y - min.y; 116 | } else if(d == 2) { 117 | return max.z - min.z; 118 | } else { 119 | throw new IllegalArgumentException("Invalid dimension"); 120 | } 121 | } 122 | 123 | @Override 124 | public boolean contains(final HyperRect r) { 125 | final Rect3d r2 = (Rect3d)r; 126 | 127 | return min.x <= r2.min.x && 128 | max.x >= r2.max.x && 129 | min.y <= r2.min.y && 130 | max.y >= r2.max.y && 131 | min.z <= r2.min.z && 132 | max.z >= r2.max.z; 133 | } 134 | 135 | @Override 136 | public boolean intersects(final HyperRect r) { 137 | final Rect3d r2 = (Rect3d)r; 138 | 139 | return !(min.x > r2.max.x) && 140 | !(r2.min.x > max.x) && 141 | !(min.y > r2.max.y) && 142 | !(r2.min.y > max.y) && 143 | !(min.z > r2.max.z) && 144 | !(r2.min.z > max.z); 145 | } 146 | 147 | @Override 148 | public double cost() { 149 | final double dx = max.x - min.x; 150 | final double dy = max.y - min.y; 151 | final double dz = max.z - min.z; 152 | return Math.abs(dx*dy*dz); 153 | } 154 | 155 | @Override 156 | public double perimeter() { 157 | double p = 0.0; 158 | final int nD = this.getNDim(); 159 | for(int d = 0; d { 201 | 202 | @Override 203 | public HyperRect getBBox(final Rect3d rect2D) { 204 | return rect2D; 205 | } 206 | 207 | @Override 208 | public HyperRect getMbr(final HyperPoint p1, final HyperPoint p2) { 209 | return new Rect3d(p1.getCoord(0), p1.getCoord(1), p1.getCoord(2), p2.getCoord(0), p2.getCoord(1), p2.getCoord(2)); 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/AxialSplitLeaf.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import java.util.Arrays; 24 | import java.util.Comparator; 25 | 26 | /** 27 | * Fast RTree split suggested by Yufei Tao taoyf@cse.cuhk.edu.hk 28 | * 29 | * Perform an axial split 30 | * 31 | * Created by jcairns on 5/5/15. 32 | */ 33 | final class AxialSplitLeaf extends Leaf { 34 | 35 | protected AxialSplitLeaf(final RectBuilder builder, final int mMin, final int mMax) { 36 | super(builder, mMin, mMax, RTree.Split.AXIAL); 37 | } 38 | 39 | @Override 40 | protected Node split(final T t) { 41 | final Branch pNode = new Branch<>(builder, mMin, mMax, splitType); 42 | final Node l1Node = create(builder, mMin, mMax, splitType); 43 | final Node l2Node = create(builder, mMin, mMax, splitType); 44 | final int nD = r[0].getNDim(); 45 | 46 | // choose axis to split 47 | int axis = 0; 48 | double rangeD = mbr.getRange(0); 49 | for(int d=1; d rangeD) { 53 | axis = d; 54 | rangeD = dr; 55 | } 56 | } 57 | 58 | final int splitDimension = axis; 59 | 60 | // sort along split dimension 61 | final HyperRect[] sortedMbr = Arrays.copyOf(r, r.length); 62 | 63 | Arrays.sort(sortedMbr, new Comparator() { 64 | @Override 65 | public int compare(final HyperRect o1, final HyperRect o2) { 66 | final HyperPoint p1 = o1.getCentroid(); 67 | final HyperPoint p2 = o2.getCentroid(); 68 | 69 | return p1.getCoord(splitDimension).compareTo(p2.getCoord(splitDimension)); 70 | } 71 | }); 72 | 73 | // divide sorted leafs 74 | for(int i=0; i implements Node { 31 | 32 | private final RectBuilder builder; 33 | 34 | private final int mMax; 35 | 36 | private final int mMin; 37 | 38 | private final RTree.Split splitType; 39 | 40 | private final Node[] child; 41 | 42 | private HyperRect mbr; 43 | 44 | private int size; 45 | 46 | Branch(final RectBuilder builder, final int mMin, final int mMax, final RTree.Split splitType) { 47 | this.mMin = mMin; 48 | this.mMax = mMax; 49 | this.builder = builder; 50 | this.mbr = null; 51 | this.size = 0; 52 | this.child = new Node[mMax]; 53 | this.splitType = splitType; 54 | } 55 | 56 | /** 57 | * Add a new node to this branch's list of children 58 | * 59 | * @param n node to be added (can be leaf or branch) 60 | * @return position of the added node 61 | */ 62 | protected int addChild(final Node n) { 63 | if(size < mMax) { 64 | child[size++] = n; 65 | 66 | if(mbr != null) { 67 | mbr = mbr.getMbr(n.getBound()); 68 | } else { 69 | mbr = n.getBound(); 70 | } 71 | 72 | return size - 1; 73 | } 74 | else { 75 | throw new RuntimeException("Too many children"); 76 | } 77 | } 78 | 79 | @Override 80 | public boolean isLeaf() { 81 | return false; 82 | } 83 | 84 | @Override 85 | public HyperRect getBound() { 86 | return mbr; 87 | } 88 | 89 | /** 90 | * Adds a data entry to one of the child nodes of this branch 91 | * 92 | * @param t data entry to add 93 | * @return Node that the entry was added to 94 | */ 95 | @Override 96 | public Node add(final T t) { 97 | final HyperRect tRect = builder.getBBox(t); 98 | if(size < mMin) { 99 | for(int i=0; i nextLeaf = Leaf.create(builder, mMin, mMax, splitType); 108 | nextLeaf.add(t); 109 | final int nextChild = addChild(nextLeaf); 110 | mbr = mbr.getMbr(child[nextChild].getBound()); 111 | 112 | return this; 113 | 114 | } else { 115 | final int bestLeaf = chooseLeaf(t, tRect); 116 | 117 | child[bestLeaf] = child[bestLeaf].add(t); 118 | mbr = mbr.getMbr(child[bestLeaf].getBound()); 119 | 120 | return this; 121 | } 122 | } 123 | 124 | @Override 125 | public Node remove(final T t) { 126 | final HyperRect tRect = builder.getBBox(t); 127 | 128 | for (int i = 0; i < size; i++) { 129 | if (child[i].getBound().intersects(tRect)) { 130 | child[i] = child[i].remove(t); 131 | 132 | if (child[i] == null) { 133 | System.arraycopy(child, i+1, child, i, size-i-1); 134 | size--; 135 | child[size] = null; 136 | if(size > 0) i--; 137 | } 138 | } 139 | } 140 | 141 | if (size == 0) { 142 | return null; 143 | } else if (size == 1) { 144 | // unsplit branch 145 | return child[0]; 146 | } 147 | 148 | mbr = child[0].getBound(); 149 | for(int i=1; i update(final T told, final T tnew) { 158 | final HyperRect tRect = builder.getBBox(told); 159 | for(int i = 0; i < size; i++){ 160 | if(tRect.intersects(child[i].getBound())) { 161 | child[i] = child[i].update(told, tnew); 162 | } 163 | if(i==0) { 164 | mbr = child[i].getBound(); 165 | } else { 166 | mbr = mbr.getMbr(child[i].getBound()); 167 | } 168 | } 169 | return this; 170 | } 171 | 172 | @Override 173 | public void search(HyperRect rect, Consumer consumer) { 174 | for(int i = 0; i < size; i++) { 175 | if(rect.intersects(child[i].getBound())) { 176 | child[i].search(rect, consumer); 177 | } 178 | } 179 | } 180 | 181 | @Override 182 | public int search(final HyperRect rect, final T[] t, int n) { 183 | final int tLen = t.length; 184 | final int n0 = n; 185 | for(int i=0; i < size && n < tLen; i++) { 186 | if (rect.intersects(child[i].getBound())) { 187 | n += child[i].search(rect, t, n); 188 | } 189 | } 190 | return n-n0; 191 | } 192 | 193 | @Override 194 | public void intersects(HyperRect rect, Consumer consumer) { 195 | for(int i = 0; i < size; i++) { 196 | if(rect.intersects(child[i].getBound())) { 197 | child[i].intersects(rect, consumer); 198 | } 199 | } 200 | } 201 | 202 | @Override 203 | public int intersects(final HyperRect rect, final T[] t, int n) { 204 | final int tLen = t.length; 205 | final int n0 = n; 206 | for(int i=0; i < size && n < tLen; i++) { 207 | if (rect.intersects(child[i].getBound())) { 208 | n += child[i].intersects(rect, t, n); 209 | } 210 | } 211 | return n-n0; 212 | } 213 | 214 | /** 215 | * @return number of child nodes 216 | */ 217 | @Override 218 | public int size() { 219 | return size; 220 | } 221 | 222 | @Override 223 | public int totalSize() { 224 | int s = 0; 225 | for(int i=0; i 0) { 233 | int bestNode = 0; 234 | HyperRect childMbr = child[0].getBound().getMbr(tRect); 235 | double leastEnlargement = childMbr.cost() - (child[0].getBound().cost() + tRect.cost()); 236 | double leastPerimeter = childMbr.perimeter(); 237 | 238 | for(int i = 1; i n = Leaf.create(builder, mMin, mMax, splitType); 260 | n.add(t); 261 | child[size++] = n; 262 | 263 | if(mbr == null) { 264 | mbr = n.getBound(); 265 | } 266 | else { 267 | mbr = mbr.getMbr(n.getBound()); 268 | } 269 | 270 | return size-1; 271 | } 272 | } 273 | 274 | /** 275 | * Return child nodes of this branch. 276 | * 277 | * @return array of child nodes (leaves or branches) 278 | */ 279 | public Node[] getChildren() { 280 | return child; 281 | } 282 | 283 | @Override 284 | public void forEach(Consumer consumer) { 285 | for(int i = 0; i < size; i++) { 286 | child[i].forEach(consumer); 287 | } 288 | } 289 | 290 | @Override 291 | public boolean contains(HyperRect rect, T t) { 292 | for(int i = 0; i < size; i++) { 293 | if(rect.intersects(child[i].getBound())) { 294 | child[i].contains(rect, t); 295 | } 296 | } 297 | return false; 298 | } 299 | 300 | @Override 301 | public void collectStats(Stats stats, int depth) { 302 | for(int i = 0; i < size; i++) { 303 | child[i].collectStats(stats, depth + 1); 304 | } 305 | stats.countBranchAtDepth(depth); 306 | } 307 | 308 | @Override 309 | public String toString() { 310 | final StringBuilder sb = new StringBuilder(128); 311 | sb.append("BRANCH["); 312 | sb.append(mbr); 313 | sb.append(']'); 314 | 315 | return sb.toString(); 316 | } 317 | 318 | @Override 319 | public Node instrument() { 320 | for(int i = 0; i < size; i++) { 321 | child[i] = child[i].instrument(); 322 | } 323 | return new CounterNode<>(this); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/ConcurrentRTree.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import java.util.concurrent.locks.Lock; 24 | import java.util.concurrent.locks.ReadWriteLock; 25 | import java.util.function.Consumer; 26 | import java.util.Collection; 27 | 28 | /** 29 | * Created by jcovert on 12/30/15. 30 | */ 31 | public class ConcurrentRTree implements SpatialSearch { 32 | 33 | private final SpatialSearch rTree; 34 | private final Lock readLock; 35 | private final Lock writeLock; 36 | 37 | protected ConcurrentRTree(SpatialSearch rTree, ReadWriteLock lock) { 38 | this.rTree = rTree; 39 | this.readLock = lock.readLock(); 40 | this.writeLock = lock.writeLock(); 41 | } 42 | 43 | @Override 44 | public int intersects(HyperRect rect, T[] t) { 45 | readLock.lock(); 46 | try { 47 | return rTree.intersects(rect, t); 48 | } finally { 49 | readLock.unlock(); 50 | } 51 | } 52 | 53 | @Override 54 | public void intersects(HyperRect rect, Consumer consumer) { 55 | readLock.lock(); 56 | try { 57 | rTree.intersects(rect, consumer); 58 | } finally { 59 | readLock.unlock(); 60 | } 61 | } 62 | 63 | /** 64 | * Blocking locked search 65 | * 66 | * @param rect - HyperRect to search 67 | * @param t - array to hold results 68 | * 69 | * @return number of entries found 70 | */ 71 | @Override 72 | public int search(final HyperRect rect, final T[] t) { 73 | readLock.lock(); 74 | try { 75 | return rTree.search(rect, t); 76 | } 77 | finally { 78 | readLock.unlock(); 79 | } 80 | } 81 | 82 | /** 83 | * Blocking locked add 84 | * 85 | * @param t - entry to add 86 | */ 87 | @Override 88 | public void add(final T t) { 89 | writeLock.lock(); 90 | try { 91 | rTree.add(t); 92 | } 93 | finally { 94 | writeLock.unlock(); 95 | } 96 | } 97 | 98 | /** 99 | * Blocking locked remove 100 | * 101 | * @param t - entry to remove 102 | */ 103 | @Override 104 | public void remove(final T t) { 105 | writeLock.lock(); 106 | try { 107 | rTree.remove(t); 108 | } 109 | finally { 110 | writeLock.unlock(); 111 | } 112 | } 113 | 114 | /** 115 | * Blocking locked update 116 | * 117 | * @param told - entry to update 118 | * @param tnew - entry with new value 119 | */ 120 | @Override 121 | public void update(final T told, final T tnew) { 122 | writeLock.lock(); 123 | try { 124 | rTree.update(told, tnew); 125 | } 126 | finally { 127 | writeLock.unlock(); 128 | } 129 | } 130 | 131 | /** 132 | * Non-blocking locked search 133 | * 134 | * @param rect - HyperRect to search 135 | * @param t - array to hold results 136 | * 137 | * @return number of entries found or -1 if lock was not acquired 138 | */ 139 | public int trySearch(final HyperRect rect, final T[] t) { 140 | if(readLock.tryLock()) { 141 | try { 142 | return rTree.search(rect, t); 143 | } finally { 144 | readLock.unlock(); 145 | } 146 | } 147 | return -1; 148 | } 149 | 150 | /** 151 | * Non-blocking locked add 152 | * 153 | * @param t - entry to add 154 | * 155 | * @return true if lock was acquired, false otherwise 156 | */ 157 | public boolean tryAdd(T t) { 158 | if(writeLock.tryLock()) { 159 | try { 160 | rTree.add(t); 161 | } finally { 162 | writeLock.unlock(); 163 | } 164 | return true; 165 | } 166 | return false; 167 | } 168 | 169 | /** 170 | * Non-blocking locked remove 171 | * 172 | * @param t - entry to remove 173 | * 174 | * @return true if lock was acquired, false otherwise 175 | */ 176 | public boolean tryRemove(T t) { 177 | if(writeLock.tryLock()) { 178 | try { 179 | rTree.remove(t); 180 | } finally { 181 | writeLock.unlock(); 182 | } 183 | return true; 184 | } 185 | return false; 186 | } 187 | 188 | /** 189 | * Non-blocking locked update 190 | * 191 | * @param told - entry to update 192 | * @param tnew - entry with new values 193 | * 194 | * @return true if lock was acquired, false otherwise 195 | */ 196 | public boolean tryUpdate(T told, T tnew) { 197 | if(writeLock.tryLock()) { 198 | try { 199 | rTree.update(told, tnew); 200 | } finally { 201 | writeLock.unlock(); 202 | } 203 | return true; 204 | } 205 | return false; 206 | } 207 | 208 | @Override 209 | public int getEntryCount() { 210 | return rTree.getEntryCount(); 211 | } 212 | 213 | @Override 214 | public void forEach(final Consumer consumer) { 215 | readLock.lock(); 216 | try { 217 | rTree.forEach(consumer); 218 | } finally { 219 | readLock.unlock(); 220 | } 221 | } 222 | 223 | @Override 224 | public void search(final HyperRect rect, final Consumer consumer) { 225 | readLock.lock(); 226 | try { 227 | rTree.search(rect, consumer); 228 | } finally { 229 | readLock.unlock(); 230 | } 231 | } 232 | 233 | @Override 234 | public void search(final HyperRect rect, final Collection collection) { 235 | readLock.lock(); 236 | try { 237 | rTree.search(rect, collection); 238 | } finally { 239 | readLock.unlock(); 240 | } 241 | } 242 | 243 | @Override 244 | public boolean contains(T t) { 245 | readLock.lock(); 246 | try { 247 | return rTree.contains(t); 248 | } finally { 249 | readLock.unlock(); 250 | } 251 | } 252 | 253 | @Override 254 | public Stats collectStats() { 255 | readLock.lock(); 256 | try { 257 | return rTree.collectStats(); 258 | } finally { 259 | readLock.unlock(); 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/CounterNode.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import java.util.function.Consumer; 24 | 25 | /** 26 | * Created by jcovert on 6/18/15. 27 | */ 28 | final class CounterNode implements Node { 29 | private final Node node; 30 | 31 | static int searchCount = 0; 32 | static int bboxEvalCount = 0; 33 | 34 | CounterNode(final Node node) { 35 | this.node = node; 36 | } 37 | 38 | @Override 39 | public boolean isLeaf() { 40 | return node.isLeaf(); 41 | } 42 | 43 | @Override 44 | public HyperRect getBound() { 45 | return node.getBound(); 46 | } 47 | 48 | @Override 49 | public Node add(T t) { 50 | return node.add(t); 51 | } 52 | 53 | @Override 54 | public Node remove(T t) { return node.remove(t); } 55 | 56 | @Override 57 | public Node update(T told, T tnew) { return node.update(told, tnew); } 58 | 59 | @Override 60 | public int search(HyperRect rect, T[] t, int n) { 61 | searchCount++; 62 | bboxEvalCount += node.size(); 63 | return node.search(rect, t, n); 64 | } 65 | 66 | @Override 67 | public int size() { 68 | return node.size(); 69 | } 70 | 71 | @Override 72 | public int totalSize() { 73 | return node.totalSize(); 74 | } 75 | 76 | @Override 77 | public void forEach(Consumer consumer) { 78 | node.forEach(consumer); 79 | } 80 | 81 | @Override 82 | public void search(HyperRect rect, Consumer consumer) { 83 | node.search(rect, consumer); 84 | } 85 | 86 | @Override 87 | public int intersects(HyperRect rect, T[] t, int n) { 88 | return node.intersects(rect, t, n); 89 | } 90 | 91 | @Override 92 | public void intersects(HyperRect rect, Consumer consumer) { 93 | node.intersects(rect, consumer); 94 | } 95 | 96 | @Override 97 | public boolean contains(HyperRect rect, T t) { 98 | return node.contains(rect, t); 99 | } 100 | 101 | @Override 102 | public void collectStats(Stats stats, int depth) { 103 | node.collectStats(stats, depth); 104 | } 105 | 106 | @Override 107 | public Node instrument() { 108 | return this; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/HyperPoint.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | /** 24 | * N dimensional point used to signify the bounds of a HyperRect 25 | * 26 | * Created by jcairns on 5/5/15. 27 | */ 28 | public interface HyperPoint { 29 | 30 | /** 31 | * The number of dimensions represented by this point 32 | * 33 | * @return dimension count 34 | */ 35 | int getNDim(); 36 | 37 | /** 38 | * Get the value of this point in the given dimension 39 | * 40 | * @param d - dimension 41 | * 42 | * @param - A comparable coordinate 43 | * 44 | * @return D - value of this point in the dimension 45 | * @throws IllegalArgumentException if a non-existent dimension is requested 46 | */ 47 | > D getCoord(int d); 48 | 49 | /** 50 | * Calculate the distance from this point to the given point across all dimensions 51 | * 52 | * @param p - point to calculate distance to 53 | * 54 | * @return distance to the point 55 | * @throws IllegalArgumentException if a non-existent dimension is requested 56 | */ 57 | double distance(HyperPoint p); 58 | 59 | /** 60 | * Calculate the distance from this point to the given point in a specific dimension 61 | * 62 | * @param p - point to calculate distance to 63 | * @param d - dimension to use in calculation 64 | * 65 | * @return distance to the point in the fiven dimension 66 | */ 67 | double distance(HyperPoint p, int d); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/HyperRect.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | /** 24 | * An N dimensional rectangle or "hypercube" that is a representation of a data entry. 25 | * 26 | * Created by jcairns on 4/30/15. 27 | */ 28 | public interface HyperRect> { 29 | 30 | /** 31 | * Calculate the resulting mbr when combining param HyperRect with this HyperRect 32 | * 33 | * @param r - mbr to add 34 | * 35 | * @return new HyperRect representing mbr of both HyperRects combined 36 | */ 37 | HyperRect getMbr(HyperRect r); 38 | 39 | /** 40 | * Get number of dimensions used in creating the HyperRect 41 | * 42 | * @return number of dimensions 43 | */ 44 | int getNDim(); 45 | 46 | /** 47 | * Get the minimum HyperPoint of this HyperRect 48 | * 49 | * @return min HyperPoint 50 | */ 51 | HyperPoint getMin(); 52 | 53 | /** 54 | * Get the minimum HyperPoint of this HyperRect 55 | * 56 | * @return min HyperPoint 57 | */ 58 | HyperPoint getMax(); 59 | 60 | /** 61 | * Get the HyperPoint representing the center point in all dimensions of this HyperRect 62 | * 63 | * @return middle HyperPoint 64 | */ 65 | HyperPoint getCentroid(); 66 | 67 | /** 68 | * Calculate the distance between the min and max HyperPoints in given dimension 69 | * 70 | * @param d - dimension to calculate 71 | * 72 | * @return double - the numeric range of the dimention (min - max) 73 | */ 74 | double getRange(final int d); 75 | 76 | /** 77 | * Determines if this HyperRect fully encloses parameter HyperRect 78 | * 79 | * @param r - HyperRect to test 80 | * 81 | * @return true if contains, false otherwise 82 | */ 83 | boolean contains(HyperRect r); 84 | 85 | /** 86 | * Determines if this HyperRect intersects parameter HyperRect on any axis 87 | * 88 | * @param r - HyperRect to test 89 | * 90 | * @return true if intersects, false otherwise 91 | */ 92 | boolean intersects(HyperRect r); 93 | 94 | /** 95 | * Calculate the "cost" of this HyperRect - usually the area across all dimensions 96 | * 97 | * @return - cost 98 | */ 99 | double cost(); 100 | 101 | /** 102 | * Calculate the perimeter of this HyperRect - across all dimesnions 103 | * 104 | * @return - perimeter 105 | */ 106 | double perimeter(); 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/Leaf.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import java.util.function.Consumer; 24 | 25 | /** 26 | * Node that will contain the data entries. Implemented by different type of SplitType leaf classes. 27 | * 28 | * Created by jcairns on 4/30/15. 29 | */ 30 | abstract class Leaf implements Node { 31 | 32 | protected final int mMax; // max entries per node 33 | 34 | protected final int mMin; // least number of entries per node 35 | 36 | protected final RTree.Split splitType; 37 | 38 | protected final HyperRect[] r; 39 | 40 | protected final T[] entry; 41 | 42 | protected final RectBuilder builder; 43 | 44 | protected HyperRect mbr; 45 | 46 | protected int size; 47 | 48 | protected Leaf(final RectBuilder builder, final int mMin, final int mMax, final RTree.Split splitType) { 49 | this.mMin = mMin; 50 | this.mMax = mMax; 51 | this.mbr = null; 52 | this.builder = builder; 53 | this.r = new HyperRect[mMax]; 54 | this.entry = (T[]) new Object[mMax]; 55 | this.size = 0; 56 | this.splitType = splitType; 57 | } 58 | 59 | @Override 60 | public Node add(final T t) { 61 | if(size < mMax) { 62 | final HyperRect tRect = builder.getBBox(t); 63 | if(mbr != null) { 64 | mbr = mbr.getMbr(tRect); 65 | } else { 66 | mbr = tRect; 67 | } 68 | 69 | r[size] = tRect; 70 | entry[size++] = t; 71 | } else { 72 | return split(t); 73 | } 74 | 75 | return this; 76 | } 77 | 78 | @Override 79 | public Node remove(final T t) { 80 | 81 | int i=0; 82 | int j; 83 | 84 | while(i update(final T told, final T tnew) { 133 | final HyperRect bbox = builder.getBBox(tnew); 134 | 135 | for(int i=0; i consumer) { 166 | for(int i = 0; i < size; i++) { 167 | if(rect.contains(r[i])) { 168 | consumer.accept(entry[i]); 169 | } 170 | } 171 | } 172 | 173 | @Override 174 | public int intersects(final HyperRect rect, final T[] t, int n) { 175 | final int tLen = t.length; 176 | final int n0 = n; 177 | 178 | for(int i=0; i consumer) { 188 | for(int i = 0; i < size; i++) { 189 | if(rect.intersects(r[i])) { 190 | consumer.accept(entry[i]); 191 | } 192 | } 193 | } 194 | 195 | @Override 196 | public int size() { 197 | return size; 198 | } 199 | 200 | @Override 201 | public int totalSize() { 202 | return size; 203 | } 204 | 205 | @Override 206 | public boolean isLeaf() { 207 | return true; 208 | } 209 | 210 | @Override 211 | public HyperRect getBound() { 212 | return mbr; 213 | } 214 | 215 | static Node create(final RectBuilder builder, final int mMin, final int M, final RTree.Split splitType) { 216 | 217 | switch(splitType) { 218 | case LINEAR: 219 | return new LinearSplitLeaf<>(builder, mMin, M); 220 | case QUADRATIC: 221 | return new QuadraticSplitLeaf<>(builder, mMin, M); 222 | case AXIAL: 223 | default: 224 | return new AxialSplitLeaf<>(builder, mMin, M); 225 | 226 | } 227 | 228 | } 229 | 230 | /** 231 | * Splits a leaf node that has the maximum number of entries into 2 leaf nodes of the same type with half 232 | * of the entries in each one. 233 | * 234 | * @param t entry to be added to the full leaf node 235 | * @return newly created node storing half the entries of this node 236 | */ 237 | protected abstract Node split(final T t); 238 | 239 | @Override 240 | public void forEach(Consumer consumer) { 241 | for(int i = 0; i < size; i++) { 242 | consumer.accept(entry[i]); 243 | } 244 | } 245 | 246 | @Override 247 | public boolean contains(HyperRect rect, T t) { 248 | for(int i = 0; i < size; i++) { 249 | if(rect.contains(r[i])) { 250 | if(entry[i].equals(t)) { 251 | return true; 252 | } 253 | } 254 | } 255 | return false; 256 | } 257 | 258 | @Override 259 | public void collectStats(Stats stats, int depth) { 260 | if (depth > stats.getMaxDepth()) { 261 | stats.setMaxDepth(depth); 262 | } 263 | stats.countLeafAtDepth(depth); 264 | stats.countEntriesAtDepth(size, depth); 265 | } 266 | 267 | /** 268 | * Figures out which newly made leaf node (see split method) to add a data entry to. 269 | * 270 | * @param l1Node left node 271 | * @param l2Node right node 272 | * @param t data entry to be added 273 | */ 274 | protected final void classify(final Node l1Node, final Node l2Node, final T t) { 275 | final HyperRect tRect = builder.getBBox(t); 276 | final HyperRect l1Mbr = l1Node.getBound().getMbr(tRect); 277 | final HyperRect l2Mbr = l2Node.getBound().getMbr(tRect); 278 | final double l1CostInc = Math.max(l1Mbr.cost() - (l1Node.getBound().cost() + tRect.cost()), 0.0); 279 | final double l2CostInc = Math.max(l2Mbr.cost() - (l2Node.getBound().cost() + tRect.cost()), 0.0); 280 | if(l2CostInc > l1CostInc) { 281 | l1Node.add(t); 282 | } else if(RTree.isEqual(l1CostInc, l2CostInc)) { 283 | final double l1MbrCost = l1Mbr.cost(); 284 | final double l2MbrCost = l2Mbr.cost(); 285 | if(l1MbrCost < l2MbrCost) { 286 | l1Node.add(t); 287 | } else if(RTree.isEqual(l1MbrCost, l2MbrCost)) { 288 | final double l1MbrMargin = l1Mbr.perimeter(); 289 | final double l2MbrMargin = l2Mbr.perimeter(); 290 | if(l1MbrMargin < l2MbrMargin) { 291 | l1Node.add(t); 292 | } else if(RTree.isEqual(l1MbrMargin, l2MbrMargin)) { 293 | // break ties with least number 294 | if (l1Node.size() < l2Node.size()) { 295 | l1Node.add(t); 296 | } else { 297 | l2Node.add(t); 298 | } 299 | } else { 300 | l2Node.add(t); 301 | } 302 | } else { 303 | l2Node.add(t); 304 | } 305 | } 306 | else { 307 | l2Node.add(t); 308 | } 309 | 310 | } 311 | 312 | 313 | @Override 314 | public String toString() { 315 | final StringBuilder sb = new StringBuilder(128); 316 | sb.append(splitType.name()); 317 | sb.append('['); 318 | sb.append(mbr); 319 | sb.append(']'); 320 | 321 | return sb.toString(); 322 | } 323 | 324 | @Override 325 | public Node instrument() { 326 | return new CounterNode<>(this); 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/LinearSplitLeaf.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | /** 24 | * Guttmann's Linear split 25 | * 26 | * Created by jcairns on 5/5/15. 27 | */ 28 | final class LinearSplitLeaf extends Leaf { 29 | 30 | protected LinearSplitLeaf(final RectBuilder builder, final int mMin, final int mMax) { 31 | super(builder, mMin, mMax, RTree.Split.LINEAR); 32 | } 33 | 34 | @Override 35 | protected Node split(final T t) { 36 | final Branch pNode = new Branch<>(builder, mMin, mMax, splitType); 37 | final Node l1Node = create(builder, mMin, mMax, splitType); 38 | final Node l2Node = create(builder, mMin, mMax, splitType); 39 | 40 | final int MIN = 0; 41 | final int MAX = 1; 42 | final int NRANGE = 2; 43 | final int nD = r[0].getNDim(); 44 | final int[][][] rIndex = new int[nD][NRANGE][NRANGE]; 45 | // separation between min and max extremes 46 | final double[] separation = new double[nD]; 47 | 48 | for(int d = 0; d < nD; d++) { 49 | 50 | rIndex[d][MIN][MIN] = 0; 51 | rIndex[d][MIN][MAX] = 0; 52 | rIndex[d][MAX][MIN] = 0; 53 | rIndex[d][MAX][MAX] = 0; 54 | 55 | for(int j = 1; j < size; j++) { 56 | if(r[rIndex[d][MIN][MIN]].getMin().getCoord(d).compareTo(r[j].getMin().getCoord(d)) > 0) { 57 | rIndex[d][MIN][MIN] = j; 58 | } 59 | 60 | if(r[rIndex[d][MIN][MAX]].getMin().getCoord(d).compareTo(r[j].getMin().getCoord(d)) < 0) { 61 | rIndex[d][MIN][MAX] = j; 62 | } 63 | 64 | if(r[rIndex[d][MAX][MIN]].getMax().getCoord(d).compareTo(r[j].getMax().getCoord(d)) > 0) { 65 | rIndex[d][MAX][MIN] = j; 66 | } 67 | 68 | if(r[rIndex[d][MAX][MAX]].getMax().getCoord(d).compareTo(r[j].getMax().getCoord(d)) < 0) { 69 | rIndex[d][MAX][MAX] = j; 70 | } 71 | } 72 | 73 | // highest max less lowest min 74 | final double width = r[rIndex[d][MAX][MAX]].getMax().distance(r[rIndex[d][MIN][MIN]].getMin(), d); 75 | 76 | // lowest max less highest min (normalized) 77 | separation[d] = r[rIndex[d][MAX][MIN]].getMax().distance(r[rIndex[d][MIN][MAX]].getMin(), d) / width; 78 | } 79 | 80 | int r1Ext = rIndex[0][MAX][MIN], r2Ext = rIndex[0][MIN][MAX]; 81 | double highSep = separation[0]; 82 | for(int d=1; d { 29 | 30 | /** 31 | * @return boolean - true if this node is a leaf 32 | */ 33 | boolean isLeaf(); 34 | 35 | /** 36 | * @return Rect - the bounding rectangle for this node 37 | */ 38 | HyperRect getBound(); 39 | 40 | /** 41 | * Add t to the index 42 | * 43 | * @param t - value to add to index 44 | */ 45 | Node add(T t); 46 | 47 | /** 48 | * Remove t from the index 49 | * 50 | * @param t - value to remove from index 51 | */ 52 | Node remove(T t); 53 | 54 | /** 55 | * update an existing t in the index 56 | * 57 | * @param told - old index to be updated 58 | * @param tnew - value to update old index to 59 | */ 60 | Node update(T told, T tnew); 61 | 62 | /** 63 | * Search for rect within this node 64 | * 65 | * @param rect - HyperRect to search for 66 | * @param t - array of found results 67 | * @param n - total result count so far (from recursive call) 68 | * @return result count from search of this node 69 | */ 70 | int search(HyperRect rect, T[] t, int n); 71 | 72 | /** 73 | * Visitor pattern: 74 | * 75 | * Consumer "accepts" every node contained by the given rect 76 | * 77 | * @param rect - limiting rect 78 | * @param consumer 79 | */ 80 | void search(HyperRect rect, Consumer consumer); 81 | 82 | /** 83 | * intersect rect with this node 84 | * 85 | * @param rect - HyperRect to search for 86 | * @param t - array of found results 87 | * @param n - total result count so far (from recursive call) 88 | * @return result count from search of this node 89 | */ 90 | int intersects(HyperRect rect, T[] t, int n); 91 | 92 | /** 93 | * Visitor pattern: 94 | * 95 | * Consumer "accepts" every node intersecting the given rect 96 | * 97 | * @param rect - limiting rect 98 | * @param consumer 99 | */ 100 | void intersects(HyperRect rect, Consumer consumer); 101 | 102 | 103 | /** 104 | * 105 | * @param rect 106 | * @param t 107 | * @return boolean true if subtree contains t 108 | */ 109 | boolean contains(HyperRect rect, T t); 110 | 111 | /** 112 | * The number of entries in the node 113 | * 114 | * @return int - entry count 115 | */ 116 | int size(); 117 | 118 | /** 119 | * The number of entries in the subtree 120 | * 121 | * @return int - entry count 122 | */ 123 | int totalSize(); 124 | 125 | /** 126 | * Consumer "accepts" every node in the entire index 127 | * 128 | * @param consumer 129 | */ 130 | void forEach(Consumer consumer); 131 | 132 | /** 133 | * Recurses over index collecting stats 134 | * 135 | * @param stats - Stats object being populated 136 | * @param depth - current depth in tree 137 | */ 138 | void collectStats(Stats stats, int depth); 139 | 140 | /** 141 | * Visits node, wraps it in an instrumented node, (see CounterNode) 142 | * 143 | * @return instrumented node wrapper 144 | */ 145 | Node instrument(); 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/QuadraticSplitLeaf.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | /** 24 | * Guttmann's Quadratic split 25 | * 26 | * Created by jcairns on 5/5/15. 27 | */ 28 | final class QuadraticSplitLeaf extends Leaf { 29 | 30 | protected QuadraticSplitLeaf(final RectBuilder builder, final int mMin, final int mMax) { 31 | super(builder, mMin, mMax, RTree.Split.QUADRATIC); 32 | } 33 | 34 | @Override 35 | protected Node split(final T t) { 36 | 37 | final Branch pNode = new Branch<>(builder, mMin, mMax, splitType); 38 | final Node l1Node = create(builder, mMin, mMax, splitType); 39 | final Node l2Node = create(builder, mMin, mMax, splitType); 40 | 41 | // find the two rectangles that are most wasteful 42 | double minCost = Double.MIN_VALUE; 43 | int r1Max=0, r2Max=size-1; 44 | for(int i=0; i minCost) { 49 | r1Max = i; 50 | r2Max = j; 51 | minCost = cost; 52 | } 53 | } 54 | } 55 | 56 | // two seeds 57 | l1Node.add(entry[r1Max]); 58 | l2Node.add(entry[r2Max]); 59 | 60 | for(int i=0; iData structure to make range searching more efficient. Indexes multi-dimensional information 28 | * such as geographical coordinates or rectangles. Groups information and represents them with a 29 | * minimum bounding rectangle (mbr). When searching through the tree, any query that does not 30 | * intersect an mbr can ignore any data entries in that mbr.

31 | *

More information can be found here @see https://en.wikipedia.org/wiki/R-tree

32 | *

33 | * Created by jcairns on 4/30/15.

34 | */ 35 | public final class RTree implements SpatialSearch { 36 | private static final double EPSILON = 1e-12; 37 | 38 | private final int mMin; 39 | private final int mMax; 40 | private final RectBuilder builder; 41 | private final Split splitType; 42 | 43 | private Node root = null; 44 | 45 | protected RTree(final RectBuilder builder, final int mMin, final int mMax, final Split splitType) { 46 | this.mMin = mMin; 47 | this.mMax = mMax; 48 | this.builder = builder; 49 | this.splitType = splitType; 50 | } 51 | 52 | @Override 53 | public int search(final HyperRect rect, final T[] t) { 54 | if(root != null) { 55 | return root.search(rect, t, 0); 56 | } 57 | return 0; 58 | } 59 | 60 | @Override 61 | public void search(HyperRect rect, Consumer consumer) { 62 | if(root != null) { 63 | root.search(rect, consumer); 64 | } 65 | } 66 | 67 | @Override 68 | public void search(HyperRect rect, Collection collection) { 69 | if(root != null) { 70 | root.search(rect, t -> collection.add(t)); 71 | } 72 | } 73 | 74 | @Override 75 | public int intersects(final HyperRect rect, final T[] t) { 76 | if(root != null) { 77 | return root.intersects(rect, t, 0); 78 | } 79 | return 0; 80 | } 81 | 82 | @Override 83 | public void intersects(HyperRect rect, Consumer consumer) { 84 | if(root != null) { 85 | root.intersects(rect, consumer); 86 | } 87 | } 88 | 89 | @Override 90 | public void add(final T t) { 91 | if(root != null) { 92 | root = root.add(t); 93 | } else { 94 | root = Leaf.create(builder, mMin, mMax, splitType); 95 | root.add(t); 96 | } 97 | } 98 | 99 | @Override 100 | public void remove(final T t) { 101 | if(root != null) { 102 | root = root.remove(t); 103 | } 104 | } 105 | 106 | @Override 107 | public void update(final T told, final T tnew) { 108 | if(root != null) { 109 | root = root.update(told, tnew); 110 | } 111 | } 112 | 113 | @Override 114 | public int getEntryCount() { 115 | if(root != null) { 116 | return root.totalSize(); 117 | } 118 | return 0; 119 | } 120 | 121 | /** 122 | * returns whether or not the HyperRect will enclose all of the data entries in t 123 | * 124 | * @param t Data entries to be evaluated 125 | * 126 | * @return boolean - Whether or not all entries lie inside rect 127 | */ 128 | @Override 129 | public boolean contains(final T t) { 130 | if(root != null) { 131 | final HyperRect bbox = builder.getBBox(t); 132 | return root.contains(bbox, t); 133 | } 134 | return false; 135 | } 136 | 137 | public static boolean isEqual(final double a, final double b) { 138 | return isEqual(a, b, EPSILON); 139 | } 140 | 141 | static boolean isEqual(final double a, final double b, final double eps) { 142 | return Math.abs(a - b) <= ((Math.abs(a) < Math.abs(b) ? Math.abs(b) : Math.abs(a)) * eps); 143 | } 144 | 145 | @Override 146 | public void forEach(Consumer consumer) { 147 | if(root != null) { 148 | root.forEach(consumer); 149 | } 150 | } 151 | 152 | void instrumentTree() { 153 | if(root != null) { 154 | root = root.instrument(); 155 | ((CounterNode) root).searchCount = 0; 156 | ((CounterNode) root).bboxEvalCount = 0; 157 | } 158 | } 159 | 160 | @Override 161 | public Stats collectStats() { 162 | Stats stats = new Stats(); 163 | stats.setType(splitType); 164 | stats.setMaxFill(mMax); 165 | stats.setMinFill(mMin); 166 | root.collectStats(stats, 0); 167 | return stats; 168 | } 169 | 170 | Node getRoot() { 171 | return root; 172 | } 173 | 174 | 175 | /** 176 | * Different methods for splitting nodes in an RTree. 177 | * 178 | * AXIAL has been shown to give good performance for many general spatial problems, 179 | * 180 | *

181 | * Created by ewhite on 10/28/15. 182 | */ 183 | public enum Split { 184 | AXIAL, 185 | LINEAR, 186 | QUADRATIC, 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/RectBuilder.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | /** 24 | * Created by jcairns on 4/30/15. 25 | */ 26 | public interface RectBuilder { 27 | 28 | /** 29 | * Build a bounding rectangle for the given element 30 | * 31 | * @param t - element to bound 32 | * 33 | * @return HyperRect impl for this entry 34 | */ 35 | HyperRect getBBox(T t); 36 | 37 | 38 | /** 39 | * Build a bounding rectangle for given points (min and max, usually) 40 | * 41 | * @param p1 - first point (top-left point, for example) 42 | * @param p2 - second point (bottom-right point, for example) 43 | * 44 | * @return HyperRect impl defined by two points 45 | */ 46 | HyperRect getMbr(HyperPoint p1, HyperPoint p2); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/SpatialSearch.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import java.util.function.Consumer; 24 | import java.util.Collection; 25 | 26 | /** 27 | * Created by jcovert on 12/30/15. 28 | */ 29 | public interface SpatialSearch { 30 | /** 31 | * Search for entries intersecting given bounding rect 32 | * 33 | * @param rect - Bounding rectangle to use for querying 34 | * @param t - Array to store found entries 35 | * 36 | * @return Number of results found 37 | */ 38 | int intersects(HyperRect rect, T[] t); 39 | 40 | /** 41 | * Search for entries intersecting given bounding rect 42 | * 43 | * @param rect - Bounding rectangle to use for querying 44 | * @param consumer - callback to receive intersecting objects 45 | * 46 | */ 47 | void intersects(HyperRect rect, Consumer consumer); 48 | 49 | /** 50 | * Search for entries contained by the given bounding rect 51 | * 52 | * @param rect - Bounding rectangle to use for querying 53 | * @param t - Array to store found entries 54 | * 55 | * @return Number of results found 56 | */ 57 | int search(HyperRect rect, T[] t); 58 | 59 | /** 60 | * Search for entries contained by the given bounding rect 61 | * 62 | * @param rect - Bounding rectangle to use for querying 63 | * @param consumer - callback to receive intersecting objects 64 | * 65 | */ 66 | void search(HyperRect rect, Consumer consumer); 67 | 68 | /** 69 | * Search for entries contained by the given bounding rect 70 | * 71 | * @param rect - Bounding rectangle to use for querying 72 | * @param collection - collection to receive results 73 | * 74 | */ 75 | void search(HyperRect rect, Collection collection); 76 | 77 | /** 78 | * returns whether or not the HyperRect will enclose all of the data entries in t 79 | * 80 | * @param t - entry 81 | * 82 | * @return boolean - Whether or not all entries lie inside rect 83 | */ 84 | boolean contains(T t); 85 | 86 | /** 87 | * Add the data entry to the SpatialSearch structure 88 | * 89 | * @param t Data entry to be added 90 | */ 91 | void add(T t); 92 | 93 | /** 94 | * Remove the data entry from the SpatialSearch structure 95 | * 96 | * @param t Data entry to be removed 97 | */ 98 | void remove(T t); 99 | 100 | /** 101 | * Update entry in tree 102 | * 103 | * @param told - Entry to update 104 | * @param tnew - Entry to update it to 105 | */ 106 | void update(T told, T tnew); 107 | 108 | /** 109 | * Get the number of entries in the tree 110 | * 111 | * @return entry count 112 | */ 113 | int getEntryCount(); 114 | 115 | /** 116 | * Iterate over all entries in the tree 117 | * 118 | * @param consumer - callback for each element 119 | */ 120 | void forEach(Consumer consumer); 121 | 122 | Stats collectStats(); 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/SpatialSearches.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import java.util.concurrent.locks.ReentrantReadWriteLock; 24 | 25 | /** 26 | * Create instances of SpatialSearch implementations 27 | * 28 | * Created by jcovert on 2/3/16. 29 | */ 30 | public class SpatialSearches { 31 | 32 | private static final int DEFAULT_MIN_M = 2; 33 | private static final int DEFAULT_MAX_M = 8; 34 | private static final RTree.Split DEFAULT_SPLIT_TYPE = RTree.Split.AXIAL; 35 | 36 | private SpatialSearches() {} 37 | 38 | /** 39 | * Create an R-Tree with default values for m, M, and split type 40 | * 41 | * @param builder - Builder implementation used to create HyperRects out of T's 42 | * @param - The store type of the bound 43 | * 44 | * @return SpatialSearch - The spatial search and index structure 45 | */ 46 | public static SpatialSearch rTree(final RectBuilder builder) { 47 | return new RTree<>(builder, DEFAULT_MIN_M, DEFAULT_MAX_M, DEFAULT_SPLIT_TYPE); 48 | } 49 | 50 | /** 51 | * Create an R-Tree with specified values for m, M, and split type 52 | * 53 | * @param builder - Builder implementation used to create HyperRects out of T's 54 | * @param minM - minimum number of entries per node of this tree 55 | * @param maxM - maximum number of entries per node of this tree (exceeding this causes node split) 56 | * @param splitType - type of split to use when M+1 entries are added to a node 57 | * @param - The store type of the bound 58 | * 59 | * @return SpatialSearch - The spatial search and index structure 60 | */ 61 | public static SpatialSearch rTree(final RectBuilder builder, final int minM, final int maxM, final RTree.Split splitType) { 62 | return new RTree<>(builder, minM, maxM, splitType); 63 | } 64 | 65 | /** 66 | * Create a protected R-Tree with default values for m, M, and split type 67 | * 68 | * @param builder - Builder implementation used to create HyperRects out of T's 69 | * @param - The store type of the bound 70 | * 71 | * @return SpatialSearch - The spatial search and index structure 72 | */ 73 | public static SpatialSearch lockingRTree(final RectBuilder builder) { 74 | return new ConcurrentRTree<>(rTree(builder), new ReentrantReadWriteLock(true)); 75 | } 76 | 77 | /** 78 | * Create a protected R-Tree with specified values for m, M, and split type 79 | * 80 | * @param builder - Builder implementation used to create HyperRects out of T's 81 | * @param minM - minimum number of entries per node of this tree 82 | * @param maxM - maximum number of entries per node of this tree (exceeding this causes node split) 83 | * @param splitType - type of split to use when M+1 entries are added to a node 84 | * @param - The store type of the bound 85 | * 86 | * @return SpatialSearch - The spatial search and index structure 87 | */ 88 | public static SpatialSearch lockingRTree(final RectBuilder builder, final int minM, final int maxM, final RTree.Split splitType) { 89 | return new ConcurrentRTree<>(rTree(builder, minM, maxM, splitType), new ReentrantReadWriteLock(true)); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/conversantmedia/util/collection/spatial/Stats.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import java.io.PrintStream; 24 | 25 | /** 26 | * Created by jcovert on 5/20/15. 27 | */ 28 | public class Stats { 29 | 30 | private RTree.Split type; 31 | private int maxFill; 32 | private int minFill; 33 | 34 | private int maxDepth = 0; 35 | private int branchCount = 0; 36 | private int leafCount = 0; 37 | private int entryCount = 0; 38 | private int[] entriesAtDepth = new int[1000]; 39 | private int[] branchesAtDepth = new int[1000]; 40 | private int[] leavesAtDepth = new int[1000]; 41 | 42 | public void print(PrintStream out) { 43 | out.println("[" + type + "] m=" + minFill + " M=" + maxFill); 44 | out.println(" Branches (" + branchCount + " total)"); 45 | out.print(" "); 46 | for (int i = 0; i <= maxDepth; i++) { 47 | out.print(i + ": " + branchesAtDepth[i] + " "); 48 | } 49 | out.println("\n Leaves (" + leafCount + " total)"); 50 | out.print(" "); 51 | for (int i = 0; i <= maxDepth; i++) { 52 | out.print(i + ": " + leavesAtDepth[i] + " "); 53 | } 54 | out.println("\n Entries (" + entryCount + " total)"); 55 | out.print(" "); 56 | for (int i = 0; i <= maxDepth; i++) { 57 | out.print(i + ": " + entriesAtDepth[i] + " "); 58 | } 59 | out.printf("\n Leaf Fill Percentage: %.2f%%\n", getLeafFillPercentage()); 60 | out.printf(" Entries per Leaf: %.2f\n", getEntriesPerLeaf()); 61 | out.println(" Max Depth: " + maxDepth); 62 | out.println(); 63 | } 64 | 65 | public float getEntriesPerLeaf() { 66 | return ((entryCount * 1.0f) / leafCount); 67 | } 68 | 69 | public float getLeafFillPercentage() { 70 | return (getEntriesPerLeaf() * 100) / maxFill; 71 | } 72 | 73 | public RTree.Split getType() { 74 | return type; 75 | } 76 | 77 | public void setType(RTree.Split type) { 78 | this.type = type; 79 | } 80 | 81 | public void setMaxFill(int maxFill) { 82 | this.maxFill = maxFill; 83 | } 84 | 85 | public void setMinFill(int minFill) { 86 | this.minFill = minFill; 87 | } 88 | 89 | public int getBranchCount() { 90 | return branchCount; 91 | } 92 | 93 | public int getLeafCount() { 94 | return leafCount; 95 | } 96 | 97 | public int getEntryCount() { 98 | return entryCount; 99 | } 100 | 101 | public int getMaxDepth() { 102 | return maxDepth; 103 | } 104 | 105 | public void setMaxDepth(int maxDepth) { 106 | this.maxDepth = maxDepth; 107 | } 108 | 109 | public void countEntriesAtDepth(int entries, int depth) { 110 | entryCount += entries; 111 | entriesAtDepth[depth] += entries; 112 | } 113 | 114 | public void countLeafAtDepth(int depth) { 115 | leafCount++; 116 | leavesAtDepth[depth]++; 117 | } 118 | 119 | public void countBranchAtDepth(int depth) { 120 | branchCount++; 121 | branchesAtDepth[depth]++; 122 | } 123 | } -------------------------------------------------------------------------------- /src/main/resources/RTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conversant/rtree/5123950dd43719664bb20f17f65515c9472fb49a/src/main/resources/RTree.png -------------------------------------------------------------------------------- /src/main/resources/mM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conversant/rtree/5123950dd43719664bb20f17f65515c9472fb49a/src/main/resources/mM.png -------------------------------------------------------------------------------- /src/test/java/com/conversantmedia/util/collection/spatial/AxialSplitLeafTest.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.geometry.Rect2d; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | 27 | import java.util.Random; 28 | 29 | /** 30 | * Created by jcovert on 6/12/15. 31 | */ 32 | public class AxialSplitLeafTest { 33 | 34 | private static final RTree.Split TYPE = RTree.Split.AXIAL; 35 | 36 | /** 37 | * Adds enough entries to force a single split and confirms that 38 | * no entries are lost. 39 | */ 40 | @Test 41 | public void basicSplitTest() { 42 | 43 | RTree rTree = RTreeTest.createRect2DTree(TYPE); 44 | rTree.add(new Rect2d(0, 0, 1, 1)); 45 | rTree.add(new Rect2d(1, 1, 2, 2)); 46 | rTree.add(new Rect2d(2, 2, 3, 3)); 47 | rTree.add(new Rect2d(3, 3, 4, 4)); 48 | rTree.add(new Rect2d(4, 4, 5, 5)); 49 | rTree.add(new Rect2d(5, 5, 6, 6)); 50 | rTree.add(new Rect2d(6, 6, 7, 7)); 51 | rTree.add(new Rect2d(7, 7, 8, 8)); 52 | // 9 entries guarantees a split 53 | rTree.add(new Rect2d(8, 8, 9, 9)); 54 | 55 | Stats stats = rTree.collectStats(); 56 | Assert.assertTrue("Unexpected max depth after basic split", stats.getMaxDepth() == 1); 57 | Assert.assertTrue("Unexpected number of branches after basic split", stats.getBranchCount() == 1); 58 | Assert.assertTrue("Unexpected number of leaves after basic split", stats.getLeafCount() == 2); 59 | Assert.assertTrue("Unexpected number of entries per leaf after basic split", stats.getEntriesPerLeaf() == 4.5); 60 | } 61 | 62 | @Test 63 | public void splitCorrectnessTest() { 64 | 65 | RTree rTree = RTreeTest.createRect2DTree(2, 4, TYPE); 66 | rTree.add(new Rect2d(0, 0, 3, 3)); 67 | rTree.add(new Rect2d(1, 1, 2, 2)); 68 | rTree.add(new Rect2d(2, 2, 4, 4)); 69 | rTree.add(new Rect2d(4, 0, 5, 1)); 70 | // 5 entrees guarantees a split 71 | rTree.add(new Rect2d(0, 2, 1, 4)); 72 | 73 | Branch root = (Branch) rTree.getRoot(); 74 | Node[] children = root.getChildren(); 75 | int childCount = 0; 76 | for(Node c : children) { 77 | if (c != null) { 78 | childCount++; 79 | } 80 | } 81 | Assert.assertEquals("Expected different number of children after split", 2, childCount); 82 | 83 | Node child1 = children[0]; 84 | Rect2d child1Mbr = (Rect2d) child1.getBound(); 85 | Rect2d expectedChild1Mbr = new Rect2d(0, 0, 3, 4); 86 | Assert.assertEquals("Child 1 size incorrect after split", 3, child1.size()); 87 | Assert.assertEquals("Child 1 mbr incorrect after split", expectedChild1Mbr, child1Mbr); 88 | 89 | Node child2 = children[1]; 90 | Rect2d child2Mbr = (Rect2d) child2.getBound(); 91 | Rect2d expectedChild2Mbr = new Rect2d(2, 0, 5, 4); 92 | Assert.assertEquals("Child 2 size incorrect after split", 2, child2.size()); 93 | Assert.assertEquals("Child 2 mbr incorrect after split", expectedChild2Mbr, child2Mbr); 94 | } 95 | 96 | /** 97 | * Adds several overlapping rectangles and confirms that no entries 98 | * are lost during insert/split. 99 | */ 100 | @Test 101 | public void overlappingEntryTest() { 102 | 103 | final RTree rTree = RTreeTest.createRect2DTree(TYPE); 104 | rTree.add(new Rect2d(0, 0, 1, 1)); 105 | rTree.add(new Rect2d(0, 0, 2, 2)); 106 | rTree.add(new Rect2d(0, 0, 2, 2)); 107 | rTree.add(new Rect2d(0, 0, 3, 3)); 108 | rTree.add(new Rect2d(0, 0, 3, 3)); 109 | 110 | rTree.add(new Rect2d(0, 0, 4, 4)); 111 | rTree.add(new Rect2d(0, 0, 5, 5)); 112 | rTree.add(new Rect2d(0, 0, 6, 6)); 113 | rTree.add(new Rect2d(0, 0, 7, 7)); 114 | rTree.add(new Rect2d(0, 0, 7, 7)); 115 | 116 | rTree.add(new Rect2d(0, 0, 8, 8)); 117 | rTree.add(new Rect2d(0, 0, 9, 9)); 118 | rTree.add(new Rect2d(0, 1, 2, 2)); 119 | rTree.add(new Rect2d(0, 1, 3, 3)); 120 | rTree.add(new Rect2d(0, 1, 4, 4)); 121 | 122 | rTree.add(new Rect2d(0, 1, 4, 4)); 123 | rTree.add(new Rect2d(0, 1, 5, 5)); 124 | 125 | // 17 entries guarantees *at least* 2 splits when max leaf size is 8 126 | final int expectedEntryCount = 17; 127 | 128 | final Stats stats = rTree.collectStats(); 129 | Assert.assertEquals("Unexpected number of entries in " + TYPE + " split tree: " + stats.getEntryCount() + " entries - expected: " + expectedEntryCount + " actual: " + stats.getEntryCount(), expectedEntryCount, stats.getEntryCount()); 130 | } 131 | 132 | /** 133 | * Adds many random entries and confirm that no entries 134 | * are lost during insert/split. 135 | */ 136 | @Test 137 | public void randomEntryTest() { 138 | 139 | final int entryCount = 50000; 140 | final Rect2d[] rects = RTreeTest.generateRandomRects(entryCount); 141 | 142 | final RTree rTree = RTreeTest.createRect2DTree(TYPE); 143 | for (int i = 0; i < rects.length; i++) { 144 | rTree.add(rects[i]); 145 | } 146 | 147 | final Stats stats = rTree.collectStats(); 148 | Assert.assertEquals("Unexpected number of entries in " + TYPE + " split tree: " + stats.getEntryCount() + " entries - expected: " + entryCount + " actual: " + stats.getEntryCount(), entryCount, stats.getEntryCount()); 149 | stats.print(System.out); 150 | } 151 | 152 | /** 153 | * This test previously caused a StackOverflowException on LINEAR leaf. 154 | * It has since been fixed, but keeping the test here to ensure this leaf type 155 | * never falls victim to the same issue. 156 | */ 157 | @Test 158 | public void causeLinearSplitOverflow() { 159 | final RTree rTree = RTreeTest.createRect2DTree(TYPE); 160 | final Random rand = new Random(13); 161 | for (int i = 0; i < 500; i++) { 162 | final int x1 = rand.nextInt(10); 163 | final int y1 = rand.nextInt(10); 164 | final int x2 = x1 + rand.nextInt(200); 165 | final int y2 = y1 + rand.nextInt(200); 166 | 167 | rTree.add(new Rect2d(x1, y1, x2, y2)); 168 | } 169 | final Stats stats = rTree.collectStats(); 170 | stats.print(System.out); 171 | } 172 | 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/test/java/com/conversantmedia/util/collection/spatial/ConcurrentRTreeTest.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.geometry.Rect2d; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | 27 | import java.util.concurrent.TimeUnit; 28 | import java.util.concurrent.locks.Condition; 29 | import java.util.concurrent.locks.Lock; 30 | import java.util.concurrent.locks.ReadWriteLock; 31 | import java.util.function.Consumer; 32 | import java.util.Collection; 33 | 34 | import static org.mockito.Mockito.*; 35 | 36 | /** 37 | * Created by jcovert on 12/30/15. 38 | */ 39 | public class ConcurrentRTreeTest { 40 | 41 | private static final Rect2d RECT_2_D_0 = new Rect2d(0, 0, 0, 0); 42 | private static final Rect2d RECT_2_D_1 = new Rect2d(1, 1, 1, 1); 43 | 44 | @Test 45 | public void testSearchLocking() { 46 | 47 | MockLock lock = new MockLock(); 48 | MockSearch search = new MockSearch(lock); 49 | ConcurrentRTree tree = new ConcurrentRTree<>(search, lock); 50 | 51 | // asserts proper locking 52 | tree.search(RECT_2_D_0, new Rect2d[0]); 53 | } 54 | 55 | @Test 56 | public void testAddLocking() { 57 | 58 | MockLock lock = new MockLock(); 59 | MockSearch search = new MockSearch(lock); 60 | ConcurrentRTree tree = new ConcurrentRTree<>(search, lock); 61 | 62 | // asserts proper locking 63 | tree.add(RECT_2_D_0); 64 | } 65 | 66 | @Test 67 | public void testRemoveLocking() { 68 | 69 | MockLock lock = new MockLock(); 70 | MockSearch search = new MockSearch(lock); 71 | ConcurrentRTree tree = new ConcurrentRTree<>(search, lock); 72 | 73 | // asserts proper locking 74 | tree.remove(RECT_2_D_0); 75 | } 76 | 77 | @Test 78 | public void testUpdateLocking() { 79 | 80 | MockLock lock = new MockLock(); 81 | MockSearch search = new MockSearch(lock); 82 | ConcurrentRTree tree = new ConcurrentRTree<>(search, lock); 83 | 84 | // asserts proper locking 85 | tree.update(RECT_2_D_0, RECT_2_D_1); 86 | } 87 | 88 | @Test 89 | public void testSearchLockingCount() { 90 | 91 | Lock readLock = mock(Lock.class); 92 | Lock writeLock = mock(Lock.class); 93 | ReadWriteLock lock = mock(ReadWriteLock.class); 94 | when(lock.readLock()).thenReturn(readLock); 95 | when(lock.writeLock()).thenReturn(writeLock); 96 | 97 | SpatialSearch search = RTreeTest.createRect2DTree(2, 8, RTree.Split.AXIAL); 98 | ConcurrentRTree tree = new ConcurrentRTree<>(search, lock); 99 | 100 | tree.search(RECT_2_D_0, new Rect2d[0]); 101 | 102 | verify(readLock, times(1)).lock(); 103 | verify(readLock, times(1)).unlock(); 104 | verify(writeLock, never()).lock(); 105 | verify(writeLock, never()).unlock(); 106 | } 107 | 108 | @Test 109 | public void testAddLockingCount() { 110 | 111 | Lock readLock = mock(Lock.class); 112 | Lock writeLock = mock(Lock.class); 113 | ReadWriteLock lock = mock(ReadWriteLock.class); 114 | when(lock.readLock()).thenReturn(readLock); 115 | when(lock.writeLock()).thenReturn(writeLock); 116 | 117 | SpatialSearch search = RTreeTest.createRect2DTree(2, 8, RTree.Split.AXIAL); 118 | ConcurrentRTree tree = new ConcurrentRTree<>(search, lock); 119 | 120 | tree.add(RECT_2_D_0); 121 | 122 | verify(readLock, never()).lock(); 123 | verify(readLock, never()).unlock(); 124 | verify(writeLock, times(1)).lock(); 125 | verify(writeLock, times(1)).unlock(); 126 | } 127 | 128 | @Test 129 | public void testRemoveLockingCount() { 130 | 131 | Lock readLock = mock(Lock.class); 132 | Lock writeLock = mock(Lock.class); 133 | ReadWriteLock lock = mock(ReadWriteLock.class); 134 | when(lock.readLock()).thenReturn(readLock); 135 | when(lock.writeLock()).thenReturn(writeLock); 136 | 137 | SpatialSearch search = RTreeTest.createRect2DTree(2, 8, RTree.Split.AXIAL); 138 | ConcurrentRTree tree = new ConcurrentRTree<>(search, lock); 139 | 140 | tree.remove(RECT_2_D_0); 141 | 142 | verify(readLock, never()).lock(); 143 | verify(readLock, never()).unlock(); 144 | verify(writeLock, times(1)).lock(); 145 | verify(writeLock, times(1)).unlock(); 146 | } 147 | 148 | @Test 149 | public void testUpdateLockingCount() { 150 | 151 | Lock readLock = mock(Lock.class); 152 | Lock writeLock = mock(Lock.class); 153 | ReadWriteLock lock = mock(ReadWriteLock.class); 154 | when(lock.readLock()).thenReturn(readLock); 155 | when(lock.writeLock()).thenReturn(writeLock); 156 | 157 | SpatialSearch search = RTreeTest.createRect2DTree(2, 8, RTree.Split.AXIAL); 158 | ConcurrentRTree tree = new ConcurrentRTree<>(search, lock); 159 | 160 | tree.update(RECT_2_D_0, RECT_2_D_1); 161 | 162 | verify(readLock, never()).lock(); 163 | verify(readLock, never()).unlock(); 164 | verify(writeLock, times(1)).lock(); 165 | verify(writeLock, times(1)).unlock(); 166 | } 167 | 168 | private static class MockLock implements ReadWriteLock { 169 | boolean isLocked = false; 170 | int readers = 0; 171 | 172 | @Override 173 | public Lock readLock() { 174 | return new Lock() { 175 | 176 | @Override 177 | public void lock() { 178 | Assert.assertFalse("Attempting to acquire read lock while write locked", isLocked); 179 | readers++; 180 | } 181 | 182 | @Override 183 | public void lockInterruptibly() throws InterruptedException { 184 | Assert.assertFalse("Attempting to acquire read lock while write locked", isLocked); 185 | readers++; 186 | } 187 | 188 | @Override 189 | public boolean tryLock() { 190 | Assert.assertFalse("Attempting to acquire read lock while write locked", isLocked); 191 | readers++; 192 | return true; 193 | } 194 | 195 | @Override 196 | public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 197 | Assert.assertFalse("Attempting to acquire read lock while write locked", isLocked); 198 | readers++; 199 | return true; 200 | } 201 | 202 | @Override 203 | public void unlock() { 204 | Assert.assertNotEquals("Attempting to unlock read lock without any readers", readers, 0); 205 | readers--; 206 | } 207 | 208 | @Override 209 | public Condition newCondition() { 210 | throw new UnsupportedOperationException(); 211 | } 212 | }; 213 | } 214 | 215 | @Override 216 | public Lock writeLock() { 217 | return new Lock() { 218 | 219 | @Override 220 | public void lock() { 221 | isLocked = true; 222 | Assert.assertEquals("Attempting to acquire write lock while readers are reading", readers, 0); 223 | } 224 | 225 | @Override 226 | public void lockInterruptibly() throws InterruptedException { 227 | isLocked = true; 228 | Assert.assertEquals("Attempting to acquire write lock while readers are reading", readers, 0); 229 | } 230 | 231 | @Override 232 | public boolean tryLock() { 233 | isLocked = true; 234 | Assert.assertEquals("Attempting to acquire write lock while readers are reading", readers, 0); 235 | return true; 236 | } 237 | 238 | @Override 239 | public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 240 | isLocked = true; 241 | Assert.assertEquals("Attempting to acquire write lock while readers are reading", readers, 0); 242 | return true; 243 | } 244 | 245 | @Override 246 | public void unlock() { 247 | Assert.assertTrue("Attempting to unlock write lock without any writers", isLocked); 248 | isLocked = false; 249 | Assert.assertEquals("Attempting to unlock write lock while readers are reading", readers, 0); 250 | } 251 | 252 | @Override 253 | public Condition newCondition() { 254 | throw new UnsupportedOperationException(); 255 | } 256 | }; 257 | } 258 | } 259 | 260 | private static final class MockSearch implements SpatialSearch { 261 | MockLock lock; 262 | 263 | public MockSearch(MockLock lock) { 264 | this.lock = lock; 265 | } 266 | 267 | @Override 268 | public int intersects(HyperRect rect, Object[] t) { 269 | Assert.assertNotEquals("Read lock should have reader while search in progress", lock.readers, 0); 270 | Assert.assertFalse("Attempting to read while writers are writing", lock.isLocked); 271 | return 0; 272 | } 273 | 274 | @Override 275 | public void intersects(HyperRect rect, Consumer consumer) { 276 | Assert.assertNotEquals("Read lock should have reader while search in progress", lock.readers, 0); 277 | Assert.assertFalse("Attempting to read while writers are writing", lock.isLocked); 278 | } 279 | 280 | @Override 281 | public int search(HyperRect rect, Object[] t) { 282 | Assert.assertNotEquals("Read lock should have reader while search in progress", lock.readers, 0); 283 | Assert.assertFalse("Attempting to read while writers are writing", lock.isLocked); 284 | return 0; 285 | } 286 | 287 | @Override 288 | public void search(HyperRect rect, Consumer consumer) { 289 | Assert.assertNotEquals("Read lock should have reader while search in progress", lock.readers, 0); 290 | Assert.assertFalse("Attempting to read while writers are writing", lock.isLocked); 291 | } 292 | 293 | @Override 294 | public void search(HyperRect rect, Collection collection) { 295 | Assert.assertNotEquals("Read lock should have reader while search in progress", lock.readers, 0); 296 | Assert.assertFalse("Attempting to read while writers are writing", lock.isLocked); 297 | } 298 | 299 | @Override 300 | public boolean contains(Object o) { 301 | Assert.assertNotEquals("Read lock should have reader while search in progress", lock.readers, 0); 302 | Assert.assertFalse("Attempting to read while writers are writing", lock.isLocked); 303 | return false; 304 | } 305 | 306 | @Override 307 | public void add(Object o) { 308 | Assert.assertEquals("Read lock should have no readers while write in progress", lock.readers, 0); 309 | Assert.assertTrue("Attempting to write without write lock", lock.isLocked); 310 | } 311 | 312 | @Override 313 | public void remove(Object o) { 314 | Assert.assertEquals("Read lock should have no readers while write in progress", lock.readers, 0); 315 | Assert.assertTrue("Attempting to write without write lock", lock.isLocked); 316 | } 317 | 318 | @Override 319 | public void update(Object told, Object tnew) { 320 | Assert.assertEquals("Read lock should have no readers while write in progress", lock.readers, 0); 321 | Assert.assertTrue("Attempting to write without write lock", lock.isLocked); 322 | } 323 | 324 | @Override 325 | public int getEntryCount() { 326 | return 0; 327 | } 328 | 329 | @Override 330 | public void forEach(Consumer consumer) { 331 | 332 | } 333 | 334 | @Override 335 | public Stats collectStats() { 336 | return null; 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/test/java/com/conversantmedia/util/collection/spatial/LinearSplitLeafTest.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.geometry.Rect2d; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | 27 | import java.util.Random; 28 | 29 | /** 30 | * Created by jcovert on 6/12/15. 31 | */ 32 | public class LinearSplitLeafTest { 33 | 34 | private static final RTree.Split TYPE = RTree.Split.LINEAR; 35 | 36 | /** 37 | * Adds enough entries to force a single split and confirms that 38 | * no entries are lost. 39 | */ 40 | @Test 41 | public void basicSplitTest() { 42 | 43 | RTree rTree = RTreeTest.createRect2DTree(TYPE); 44 | rTree.add(new Rect2d(0, 0, 1, 1)); 45 | rTree.add(new Rect2d(1, 1, 2, 2)); 46 | rTree.add(new Rect2d(2, 2, 3, 3)); 47 | rTree.add(new Rect2d(3, 3, 4, 4)); 48 | rTree.add(new Rect2d(4, 4, 5, 5)); 49 | rTree.add(new Rect2d(5, 5, 6, 6)); 50 | rTree.add(new Rect2d(6, 6, 7, 7)); 51 | rTree.add(new Rect2d(7, 7, 8, 8)); 52 | // 9 entries guarantees a split 53 | rTree.add(new Rect2d(8, 8, 9, 9)); 54 | 55 | Stats stats = rTree.collectStats(); 56 | Assert.assertTrue("Unexpected max depth after basic split", stats.getMaxDepth() == 1); 57 | Assert.assertTrue("Unexpected number of branches after basic split", stats.getBranchCount() == 1); 58 | Assert.assertTrue("Unexpected number of leaves after basic split", stats.getLeafCount() == 2); 59 | Assert.assertTrue("Unexpected number of entries per leaf after basic split", stats.getEntriesPerLeaf() == 4.5); 60 | } 61 | 62 | @Test 63 | public void splitCorrectnessTest() { 64 | 65 | RTree rTree = RTreeTest.createRect2DTree(2, 4, TYPE); 66 | rTree.add(new Rect2d(0, 0, 3, 3)); 67 | rTree.add(new Rect2d(1, 1, 2, 2)); 68 | rTree.add(new Rect2d(2, 2, 4, 4)); 69 | rTree.add(new Rect2d(4, 0, 5, 1)); 70 | // 5 entrees guarantees a split 71 | rTree.add(new Rect2d(0, 2, 1, 4)); 72 | 73 | Branch root = (Branch) rTree.getRoot(); 74 | Node[] children = root.getChildren(); 75 | int childCount = 0; 76 | for(Node c : children) { 77 | if (c != null) { 78 | childCount++; 79 | } 80 | } 81 | Assert.assertEquals("Expected different number of children after split", 2, childCount); 82 | 83 | Node child1 = children[0]; 84 | Rect2d child1Mbr = (Rect2d) child1.getBound(); 85 | Rect2d expectedChild1Mbr = new Rect2d(0, 0, 4, 4); 86 | Assert.assertEquals("Child 1 size incorrect after split", 4, child1.size()); 87 | Assert.assertEquals("Child 1 mbr incorrect after split", expectedChild1Mbr, child1Mbr); 88 | 89 | Node child2 = children[1]; 90 | Rect2d child2Mbr = (Rect2d) child2.getBound(); 91 | Rect2d expectedChild2Mbr = new Rect2d(4, 0, 5, 1); 92 | Assert.assertEquals("Child 2 size incorrect after split", 1, child2.size()); 93 | Assert.assertEquals("Child 2 mbr incorrect after split", expectedChild2Mbr, child2Mbr); 94 | } 95 | 96 | /** 97 | * Adds several overlapping rectangles and confirms that no entries 98 | * are lost during insert/split. 99 | */ 100 | @Test 101 | public void overlappingEntryTest() { 102 | 103 | final RTree rTree = RTreeTest.createRect2DTree(TYPE); 104 | rTree.add(new Rect2d(0, 0, 1, 1)); 105 | rTree.add(new Rect2d(0, 0, 2, 2)); 106 | rTree.add(new Rect2d(0, 0, 2, 2)); 107 | rTree.add(new Rect2d(0, 0, 3, 3)); 108 | rTree.add(new Rect2d(0, 0, 3, 3)); 109 | 110 | rTree.add(new Rect2d(0, 0, 4, 4)); 111 | rTree.add(new Rect2d(0, 0, 5, 5)); 112 | rTree.add(new Rect2d(0, 0, 6, 6)); 113 | rTree.add(new Rect2d(0, 0, 7, 7)); 114 | rTree.add(new Rect2d(0, 0, 7, 7)); 115 | 116 | rTree.add(new Rect2d(0, 0, 8, 8)); 117 | rTree.add(new Rect2d(0, 0, 9, 9)); 118 | rTree.add(new Rect2d(0, 1, 2, 2)); 119 | rTree.add(new Rect2d(0, 1, 3, 3)); 120 | rTree.add(new Rect2d(0, 1, 4, 4)); 121 | 122 | rTree.add(new Rect2d(0, 1, 4, 4)); 123 | rTree.add(new Rect2d(0, 1, 5, 5)); 124 | 125 | // 17 entries guarantees *at least* 2 splits when max leaf size is 8 126 | final int expectedEntryCount = 17; 127 | 128 | final Stats stats = rTree.collectStats(); 129 | Assert.assertEquals("Unexpected number of entries in " + TYPE + " split tree: " + stats.getEntryCount() + " entries - expected: " + expectedEntryCount + " actual: " + stats.getEntryCount(), expectedEntryCount, stats.getEntryCount()); 130 | } 131 | 132 | /** 133 | * Adds many random entries to trees of different types and confirms that 134 | * no entries are lost during insert/split. 135 | */ 136 | @Test 137 | public void randomEntryTest() { 138 | 139 | final int entryCount = 50000; 140 | final Rect2d[] rects = RTreeTest.generateRandomRects(entryCount); 141 | 142 | final RTree rTree = RTreeTest.createRect2DTree(TYPE); 143 | for (int i = 0; i < rects.length; i++) { 144 | rTree.add(rects[i]); 145 | } 146 | 147 | final Stats stats = rTree.collectStats(); 148 | Assert.assertEquals("Unexpected number of entries in " + TYPE + " split tree: " + stats.getEntryCount() + " entries - expected: " + entryCount + " actual: " + stats.getEntryCount(), entryCount, stats.getEntryCount()); 149 | stats.print(System.out); 150 | } 151 | 152 | /** 153 | * This test previously caused a StackOverflowException. 154 | * It has since been fixed, but keeping the test to ensure 155 | * it doesn't happen again. 156 | */ 157 | @Test 158 | public void causeLinearSplitOverflow() { 159 | final RTree rTree = RTreeTest.createRect2DTree(2, 8, TYPE); 160 | final Random rand = new Random(13); 161 | for (int i = 0; i < 500; i++) { 162 | final int x1 = rand.nextInt(10); 163 | final int y1 = rand.nextInt(10); 164 | final int x2 = x1 + rand.nextInt(200); 165 | final int y2 = y1 + rand.nextInt(200); 166 | 167 | rTree.add(new Rect2d(x1, y1, x2, y2)); 168 | } 169 | final Stats stats = rTree.collectStats(); 170 | stats.print(System.out); 171 | } 172 | 173 | 174 | @Test 175 | public void causeLinearSplitNiceDist() { 176 | 177 | final RTree rTree = RTreeTest.createRect2DTree(2, 8, TYPE); 178 | final Random rand = new Random(13); 179 | for (int i = 0; i < 500; i++) { 180 | final int x1 = rand.nextInt(250); 181 | final int y1 = rand.nextInt(250); 182 | final int x2 = x1 + rand.nextInt(10); 183 | final int y2 = y1 + rand.nextInt(10); 184 | 185 | rTree.add(new Rect2d(x1, y1, x2, y2)); 186 | } 187 | final Stats stats = rTree.collectStats(); 188 | stats.print(System.out); 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/test/java/com/conversantmedia/util/collection/spatial/QuadraticSplitLeafTest.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.geometry.Rect2d; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | 27 | import java.util.Random; 28 | 29 | /** 30 | * Created by jcovert on 6/12/15. 31 | */ 32 | public class QuadraticSplitLeafTest { 33 | 34 | private static final RTree.Split TYPE = RTree.Split.QUADRATIC; 35 | 36 | /** 37 | * Adds enough entries to force a single split and confirms that 38 | * no entries are lost. 39 | */ 40 | @Test 41 | public void basicSplitTest() { 42 | 43 | RTree rTree = RTreeTest.createRect2DTree(TYPE); 44 | rTree.add(new Rect2d(0, 0, 1, 1)); 45 | rTree.add(new Rect2d(1, 1, 2, 2)); 46 | rTree.add(new Rect2d(2, 2, 3, 3)); 47 | rTree.add(new Rect2d(3, 3, 4, 4)); 48 | rTree.add(new Rect2d(4, 4, 5, 5)); 49 | rTree.add(new Rect2d(5, 5, 6, 6)); 50 | rTree.add(new Rect2d(6, 6, 7, 7)); 51 | rTree.add(new Rect2d(7, 7, 8, 8)); 52 | // 9 entries guarantees a split 53 | rTree.add(new Rect2d(8, 8, 9, 9)); 54 | 55 | Stats stats = rTree.collectStats(); 56 | Assert.assertTrue("Unexpected max depth after basic split", stats.getMaxDepth() == 1); 57 | Assert.assertTrue("Unexpected number of branches after basic split", stats.getBranchCount() == 1); 58 | Assert.assertTrue("Unexpected number of leaves after basic split", stats.getLeafCount() == 2); 59 | Assert.assertTrue("Unexpected number of entries per leaf after basic split", stats.getEntriesPerLeaf() == 4.5); 60 | } 61 | 62 | @Test 63 | public void splitCorrectnessTest() { 64 | 65 | RTree rTree = RTreeTest.createRect2DTree(2, 4, TYPE); 66 | rTree.add(new Rect2d(0, 0, 3, 3)); 67 | rTree.add(new Rect2d(1, 1, 2, 2)); 68 | rTree.add(new Rect2d(2, 2, 4, 4)); 69 | rTree.add(new Rect2d(4, 0, 5, 1)); 70 | // 5 entrees guarantees a split 71 | rTree.add(new Rect2d(0, 2, 1, 4)); 72 | 73 | Branch root = (Branch) rTree.getRoot(); 74 | Node[] children = root.getChildren(); 75 | int childCount = 0; 76 | for(Node c : children) { 77 | if (c != null) { 78 | childCount++; 79 | } 80 | } 81 | Assert.assertEquals("Expected different number of children after split", 2, childCount); 82 | 83 | Node child1 = children[0]; 84 | Rect2d child1Mbr = (Rect2d) child1.getBound(); 85 | Rect2d expectedChild1Mbr = new Rect2d(0, 0, 4, 4); 86 | Assert.assertEquals("Child 1 size incorrect after split", 4, child1.size()); 87 | Assert.assertEquals("Child 1 mbr incorrect after split", expectedChild1Mbr, child1Mbr); 88 | 89 | Node child2 = children[1]; 90 | Rect2d child2Mbr = (Rect2d) child2.getBound(); 91 | Rect2d expectedChild2Mbr = new Rect2d(4, 0, 5, 1); 92 | Assert.assertEquals("Child 2 size incorrect after split", 1, child2.size()); 93 | Assert.assertEquals("Child 2 mbr incorrect after split", expectedChild2Mbr, child2Mbr); 94 | } 95 | 96 | /** 97 | * Adds several overlapping rectangles and confirms that no entries 98 | * are lost during insert/split. 99 | */ 100 | @Test 101 | public void overlappingEntryTest() { 102 | 103 | final RTree rTree = RTreeTest.createRect2DTree(TYPE); 104 | rTree.add(new Rect2d(0, 0, 1, 1)); 105 | rTree.add(new Rect2d(0, 0, 2, 2)); 106 | rTree.add(new Rect2d(0, 0, 2, 2)); 107 | rTree.add(new Rect2d(0, 0, 3, 3)); 108 | rTree.add(new Rect2d(0, 0, 3, 3)); 109 | 110 | rTree.add(new Rect2d(0, 0, 4, 4)); 111 | rTree.add(new Rect2d(0, 0, 5, 5)); 112 | rTree.add(new Rect2d(0, 0, 6, 6)); 113 | rTree.add(new Rect2d(0, 0, 7, 7)); 114 | rTree.add(new Rect2d(0, 0, 7, 7)); 115 | 116 | rTree.add(new Rect2d(0, 0, 8, 8)); 117 | rTree.add(new Rect2d(0, 0, 9, 9)); 118 | rTree.add(new Rect2d(0, 1, 2, 2)); 119 | rTree.add(new Rect2d(0, 1, 3, 3)); 120 | rTree.add(new Rect2d(0, 1, 4, 4)); 121 | 122 | rTree.add(new Rect2d(0, 1, 4, 4)); 123 | rTree.add(new Rect2d(0, 1, 5, 5)); 124 | 125 | // 17 entries guarantees *at least* 2 splits when max leaf size is 8 126 | final int expectedEntryCount = 17; 127 | 128 | final Stats stats = rTree.collectStats(); 129 | Assert.assertEquals("Unexpected number of entries in " + TYPE + " split tree: " + stats.getEntryCount() + " entries - expected: " + expectedEntryCount + " actual: " + stats.getEntryCount(), expectedEntryCount, stats.getEntryCount()); 130 | } 131 | 132 | /** 133 | * Adds many random entries to trees of different types and confirms that 134 | * no entries are lost during insertion (and split). 135 | */ 136 | @Test 137 | public void randomEntryTest() { 138 | 139 | final int entryCount = 50000; 140 | final Rect2d[] rects = RTreeTest.generateRandomRects(entryCount); 141 | 142 | final RTree rTree = RTreeTest.createRect2DTree(TYPE); 143 | for (int i = 0; i < rects.length; i++) { 144 | rTree.add(rects[i]); 145 | } 146 | 147 | final Stats stats = rTree.collectStats(); 148 | Assert.assertEquals("Unexpected number of entries in " + TYPE + " split tree: " + stats.getEntryCount() + " entries - expected: " + entryCount + " actual: " + stats.getEntryCount(), entryCount, stats.getEntryCount()); 149 | stats.print(System.out); 150 | } 151 | 152 | 153 | /** 154 | * This test previously caused a StackOverflowException on LINEAR leaf. 155 | * It has since been fixed, but keeping the test here to ensure this leaf type 156 | * never falls victim to the same issue. 157 | */ 158 | @Test 159 | public void causeLinearSplitOverflow() { 160 | final RTree rTree = RTreeTest.createRect2DTree(2, 8, TYPE); 161 | final Random rand = new Random(13); 162 | for (int i = 0; i < 500; i++) { 163 | final int x1 = rand.nextInt(10); 164 | final int y1 = rand.nextInt(10); 165 | final int x2 = x1 + rand.nextInt(200); 166 | final int y2 = y1 + rand.nextInt(200); 167 | 168 | rTree.add(new Rect2d(x1, y1, x2, y2)); 169 | } 170 | final Stats stats = rTree.collectStats(); 171 | stats.print(System.out); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/test/java/com/conversantmedia/util/collection/spatial/RTreeTest.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.geometry.Point2d; 24 | import com.conversantmedia.util.collection.geometry.Rect2d; 25 | import org.junit.Assert; 26 | import org.junit.Ignore; 27 | import org.junit.Test; 28 | 29 | import java.util.Random; 30 | import java.util.concurrent.atomic.AtomicInteger; 31 | import java.util.List; 32 | import java.util.ArrayList; 33 | 34 | /** 35 | * Created by jcairns on 4/30/15. 36 | */ 37 | public class RTreeTest { 38 | 39 | @Test 40 | public void pointSearchTest() { 41 | 42 | final RTree pTree = new RTree<>(new Point2d.Builder(), 2, 8, RTree.Split.AXIAL); 43 | 44 | for(int i=0; i<10; i++) { 45 | pTree.add(new Point2d(i, i)); 46 | } 47 | 48 | final Rect2d rect = new Rect2d(new Point2d(2,2), new Point2d(8,8)); 49 | final Point2d[] result = new Point2d[10]; 50 | 51 | final int n = pTree.search(rect, result); 52 | Assert.assertEquals(7, n); 53 | 54 | for(int i=0; i= 2); 56 | Assert.assertTrue(result[i].getCoord(Point2d.X) <= 8); 57 | Assert.assertTrue(result[i].getCoord(Point2d.Y) >= 2); 58 | Assert.assertTrue(result[i].getCoord(Point2d.Y) <= 8); 59 | } 60 | } 61 | 62 | @Test 63 | public void pointCollectionSearchTest() { 64 | 65 | final RTree pTree = new RTree<>(new Point2d.Builder(), 2, 8, RTree.Split.AXIAL); 66 | 67 | for(int i=0; i<10; i++) { 68 | pTree.add(new Point2d(i, i)); 69 | } 70 | 71 | final Rect2d rect = new Rect2d(new Point2d(2,2), new Point2d(8,8)); 72 | 73 | final List result = new ArrayList<>(); 74 | 75 | pTree.search(rect, result); 76 | 77 | final int n = result.size(); 78 | 79 | Assert.assertEquals(7, n); 80 | 81 | for(int i=0; i= 2); 83 | Assert.assertTrue(result.get(i).getCoord(Point2d.X) <= 8); 84 | Assert.assertTrue(result.get(i).getCoord(Point2d.Y) >= 2); 85 | Assert.assertTrue(result.get(i).getCoord(Point2d.Y) <= 8); 86 | } 87 | } 88 | 89 | /** 90 | * Use an small bounding box to ensure that only expected rectangles are returned. 91 | * Verifies the count returned from search AND the number of rectangles results. 92 | */ 93 | @Test 94 | public void rect2DSearchTest() { 95 | 96 | final int entryCount = 20; 97 | 98 | for (RTree.Split type : RTree.Split.values()) { 99 | RTree rTree = createRect2DTree(2, 8, type); 100 | for (int i = 0; i < entryCount; i++) { 101 | rTree.add(new Rect2d(i, i, i+3, i+3)); 102 | } 103 | 104 | final Rect2d searchRect = new Rect2d(5, 5, 10, 10); 105 | Rect2d[] results = new Rect2d[entryCount]; 106 | 107 | final int foundCount = rTree.search(searchRect, results); 108 | int resultCount = 0; 109 | for(int i = 0; i < results.length; i++) { 110 | if(results[i] != null) { 111 | resultCount++; 112 | } 113 | } 114 | 115 | final int expectedCount = 3; 116 | Assert.assertEquals("[" + type + "] Search returned incorrect search result count - expected: " + expectedCount + " actual: " + foundCount, expectedCount, foundCount); 117 | Assert.assertEquals("[" + type + "] Search returned incorrect number of rectangles - expected: " + expectedCount + " actual: " + resultCount, expectedCount, resultCount); 118 | 119 | // If the order of nodes in the tree changes, this test may fail while returning the correct results. 120 | for (int i = 0; i < resultCount; i++) { 121 | Assert.assertTrue("Unexpected result found", RTree.isEqual(results[i].getMin().getCoord(Point2d.X), i + 5) && 122 | RTree.isEqual(results[i].getMin().getCoord(Point2d.Y), i + 5) && 123 | RTree.isEqual(results[i].getMax().getCoord(Point2d.X), i + 8) && 124 | RTree.isEqual(results[i].getMax().getCoord(Point2d.Y), i + 8)); 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * Use an small bounding box to ensure that only expected rectangles are returned. 131 | * Verifies the count returned from search AND the number of rectangles results. 132 | */ 133 | @Test 134 | public void rect2DIntersectTest() { 135 | 136 | final int entryCount = 20; 137 | 138 | for (RTree.Split type : RTree.Split.values()) { 139 | RTree rTree = createRect2DTree(2, 8, type); 140 | for (int i = 0; i < entryCount; i++) { 141 | rTree.add(new Rect2d(i, i, i+3, i+3)); 142 | } 143 | 144 | final Rect2d searchRect = new Rect2d(5, 5, 10, 10); 145 | Rect2d[] results = new Rect2d[entryCount]; 146 | 147 | final int foundCount = rTree.intersects(searchRect, results); 148 | int resultCount = 0; 149 | for(int i = 0; i < results.length; i++) { 150 | if(results[i] != null) { 151 | resultCount++; 152 | } 153 | } 154 | 155 | final int expectedCount = 9; 156 | Assert.assertEquals("[" + type + "] Search returned incorrect search result count - expected: " + expectedCount + " actual: " + foundCount, expectedCount, foundCount); 157 | Assert.assertEquals("[" + type + "] Search returned incorrect number of rectangles - expected: " + expectedCount + " actual: " + resultCount, expectedCount, resultCount); 158 | 159 | // If the order of nodes in the tree changes, this test may fail while returning the correct results. 160 | for (int i = 0; i < resultCount; i++) { 161 | Assert.assertTrue("Unexpected result found", RTree.isEqual(results[i].getMin().getCoord(Point2d.X), i + 2) && 162 | RTree.isEqual(results[i].getMin().getCoord(Point2d.Y), i + 2) && 163 | RTree.isEqual(results[i].getMax().getCoord(Point2d.X), i + 5) && 164 | RTree.isEqual(results[i].getMax().getCoord(Point2d.Y), i + 5)); 165 | } 166 | } 167 | } 168 | 169 | 170 | 171 | /** 172 | * Use an enormous bounding box to ensure that every rectangle is returned. 173 | * Verifies the count returned from search AND the number of rectangles results. 174 | */ 175 | @Test 176 | public void rect2DSearchAllTest() { 177 | 178 | final int entryCount = 1000; 179 | final Rect2d[] rects = generateRandomRects(entryCount); 180 | 181 | for (RTree.Split type : RTree.Split.values()) { 182 | RTree rTree = createRect2DTree(2, 8, type); 183 | for (int i = 0; i < rects.length; i++) { 184 | rTree.add(rects[i]); 185 | } 186 | 187 | final Rect2d searchRect = new Rect2d(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE); 188 | Rect2d[] results = new Rect2d[entryCount]; 189 | 190 | final int foundCount = rTree.search(searchRect, results); 191 | int resultCount = 0; 192 | for(int i = 0; i < results.length; i++) { 193 | if(results[i] != null) { 194 | resultCount++; 195 | } 196 | } 197 | 198 | final AtomicInteger visitCount = new AtomicInteger(); 199 | rTree.search(searchRect, (n) -> {visitCount.incrementAndGet();}); 200 | Assert.assertEquals(entryCount, visitCount.get()); 201 | 202 | final int expectedCount = entryCount; 203 | Assert.assertEquals("[" + type + "] Search returned incorrect search result count - expected: " + expectedCount + " actual: " + foundCount, expectedCount, foundCount); 204 | Assert.assertEquals("[" + type + "] Search returned incorrect number of rectangles - expected: " + expectedCount + " actual: " + resultCount, expectedCount, resultCount); 205 | } 206 | } 207 | 208 | /** 209 | * Collect stats making the structure of trees of each split type 210 | * more visible. 211 | */ 212 | @Ignore 213 | // This test ignored because output needs to be manually evaluated. 214 | public void treeStructureStatsTest() { 215 | 216 | final int entryCount = 50_000; 217 | 218 | final Rect2d[] rects = generateRandomRects(entryCount); 219 | for (RTree.Split type : RTree.Split.values()) { 220 | RTree rTree = createRect2DTree(2, 8, type); 221 | for (int i = 0; i < rects.length; i++) { 222 | rTree.add(rects[i]); 223 | } 224 | 225 | Stats stats = rTree.collectStats(); 226 | stats.print(System.out); 227 | } 228 | } 229 | 230 | /** 231 | * Do a search and collect stats on how many nodes we hit and how many 232 | * bounding boxes we had to evaluate to get all the results. 233 | * 234 | * Preliminary findings: 235 | * - Evals for QUADRATIC tree increases with size of the search bounding box. 236 | * - QUADRATIC seems to be ideal for small search bounding boxes. 237 | */ 238 | @Ignore 239 | // This test ignored because output needs to be manually evaluated. 240 | public void treeSearchStatsTest() { 241 | 242 | final int entryCount = 5000; 243 | 244 | final Rect2d[] rects = generateRandomRects(entryCount); 245 | 246 | for(int j=0; j<6; j++) { 247 | for (RTree.Split type : RTree.Split.values()) { 248 | RTree rTree = createRect2DTree(2, 12, type); 249 | for (int i = 0; i < rects.length; i++) { 250 | rTree.add(rects[i]); 251 | } 252 | 253 | rTree.instrumentTree(); 254 | 255 | final Rect2d searchRect = new Rect2d(100, 100, 120, 120); 256 | Rect2d[] results = new Rect2d[entryCount]; 257 | final long start = System.nanoTime(); 258 | int foundCount = rTree.search(searchRect, results); 259 | final long end = System.nanoTime() - start; 260 | CounterNode root = (CounterNode) rTree.getRoot(); 261 | 262 | System.out.println("[" + type + "] searched " + root.searchCount + " nodes, returning " + foundCount + " entries"); 263 | System.out.println("[" + type + "] evaluated " + root.bboxEvalCount + " b-boxes, returning " + foundCount + " entries"); 264 | 265 | System.out.println("Run was " + end / 1000 + " us"); 266 | } 267 | } 268 | } 269 | 270 | @Test 271 | public void treeContainsTest() { 272 | final RTree rTree = createRect2DTree(RTree.Split.QUADRATIC); 273 | 274 | final Rect2d[] rects = new Rect2d[5]; 275 | for (int i = 0; i < rects.length; i++) { 276 | rects[i] = new Rect2d(i, i, i + 1, i + 1); 277 | rTree.add(rects[i]); 278 | } 279 | 280 | for (int i = 0; i < rects.length; i++) { 281 | Assert.assertTrue(rTree.contains(rects[i])); 282 | } 283 | } 284 | 285 | 286 | @Test 287 | public void treeRemovalTest5Entries() { 288 | final RTree rTree = createRect2DTree(RTree.Split.QUADRATIC); 289 | 290 | final Rect2d[] rects = new Rect2d[5]; 291 | for(int i = 0; i < rects.length; i++){ 292 | rects[i] = new Rect2d(i, i, i+1, i+1); 293 | rTree.add(rects[i]); 294 | } 295 | 296 | for(int i = 1; i < rects.length; i++) { 297 | rTree.remove(rects[i]); 298 | Assert.assertEquals(rects.length-i, rTree.getEntryCount()); 299 | } 300 | 301 | Assert.assertTrue("Missing hyperRect that should be found " + rects[0], rTree.contains(rects[0])); 302 | 303 | for(int i = 1; i < rects.length; i++) { 304 | Assert.assertFalse("Found hyperRect that should have been removed on search " + rects[i], rTree.contains(rects[i])); 305 | } 306 | 307 | final Rect2d hr = new Rect2d(0,0,5,5); 308 | rTree.add(hr); 309 | Assert.assertTrue(rTree.contains(hr)); 310 | Assert.assertTrue("Found hyperRect that should have been removed on search", rTree.getEntryCount() != 0); 311 | } 312 | 313 | @Test 314 | public void treeGetEntryCount() { 315 | 316 | final int NENTRY = 500; 317 | 318 | final RTree rTree = createRect2DTree(RTree.Split.QUADRATIC); 319 | 320 | for(int i = 0; i < NENTRY; i++){ 321 | final Rect2d rect = new Rect2d(i, i, i+1, i+1); 322 | rTree.add(rect); 323 | } 324 | 325 | Assert.assertEquals(NENTRY, rTree.getEntryCount()); 326 | } 327 | 328 | 329 | @Test 330 | public void treeRemovalTestDuplicates() { 331 | 332 | final int NENTRY = 50; 333 | 334 | final RTree rTree = createRect2DTree(RTree.Split.QUADRATIC); 335 | 336 | final Rect2d[] rect = new Rect2d[2]; 337 | for(int i = 0; i < rect.length; i++){ 338 | rect[i] = new Rect2d(i, i, i+1, i+1); 339 | rTree.add(rect[i]); 340 | } 341 | 342 | for(int i = 0; i< NENTRY; i++) { 343 | rTree.add(rect[1]); 344 | } 345 | 346 | Assert.assertEquals(NENTRY+2, rTree.getEntryCount()); 347 | 348 | for(int i = 0; i < rect.length; i++) { 349 | rTree.remove(rect[i]); 350 | } 351 | 352 | for(int i = 0; i < rect.length; i++) { 353 | Assert.assertFalse("Found hyperRect that should have been removed " + rect[i], rTree.contains(rect[i])); 354 | } 355 | } 356 | 357 | @Test 358 | public void treeRemovalTest1000Entries() { 359 | final RTree rTree = createRect2DTree(RTree.Split.QUADRATIC); 360 | 361 | final Rect2d[] rect = new Rect2d[1000]; 362 | for(int i = 0; i < rect.length; i++){ 363 | rect[i] = new Rect2d(i, i, i+1, i+1); 364 | rTree.add(rect[i]); 365 | } 366 | 367 | for(int i = 0; i < rect.length; i++) { 368 | rTree.remove(rect[i]); 369 | } 370 | 371 | for(int i = 0; i < rect.length; i++) { 372 | Assert.assertFalse("Found hyperRect that should have been removed" + rect[i], rTree.contains(rect[i])); 373 | } 374 | 375 | Assert.assertFalse("Found hyperRect that should have been removed on search ", rTree.getEntryCount() > 0); 376 | } 377 | 378 | @Test 379 | public void treeSingleRemovalTest() { 380 | final RTree rTree = createRect2DTree(RTree.Split.QUADRATIC); 381 | 382 | Rect2d rect = new Rect2d(0,0,2,2); 383 | rTree.add(rect); 384 | Assert.assertTrue("Did not add HyperRect to Tree", rTree.getEntryCount() > 0); 385 | rTree.remove(rect); 386 | Assert.assertTrue("Did not remove HyperRect from Tree", rTree.getEntryCount() == 0); 387 | rTree.add(rect); 388 | Assert.assertTrue("Tree nulled out and could not add HyperRect back in", rTree.getEntryCount() > 0); 389 | } 390 | 391 | @Ignore 392 | // This test ignored because output needs to be manually evaluated. 393 | public void treeRemoveAndRebalanceTest() { 394 | final RTree rTree = createRect2DTree(RTree.Split.QUADRATIC); 395 | 396 | Rect2d[] rect = new Rect2d[65]; 397 | for(int i = 0; i < rect.length; i++){ 398 | if(i < 4){ rect[i] = new Rect2d(0,0,1,1); } 399 | else if(i < 8) { rect[i] = new Rect2d(2, 2, 4, 4); } 400 | else if(i < 12) { rect[i] = new Rect2d(4,4,5,5); } 401 | else if(i < 16) { rect[i] = new Rect2d(5,5,6,6); } 402 | else if(i < 20) { rect[i] = new Rect2d(6,6,7,7); } 403 | else if(i < 24) { rect[i] = new Rect2d(7,7,8,8); } 404 | else if(i < 28) { rect[i] = new Rect2d(8,8,9,9); } 405 | else if(i < 32) { rect[i] = new Rect2d(9,9,10,10); } 406 | else if(i < 36) { rect[i] = new Rect2d(2,2,4,4); } 407 | else if(i < 40) { rect[i] = new Rect2d(4,4,5,5); } 408 | else if(i < 44) { rect[i] = new Rect2d(5,5,6,6); } 409 | else if(i < 48) { rect[i] = new Rect2d(6,6,7,7); } 410 | else if(i < 52) { rect[i] = new Rect2d(7,7,8,8); } 411 | else if(i < 56) { rect[i] = new Rect2d(8,8,9,9); } 412 | else if(i < 60) { rect[i] = new Rect2d(9,9,10,10); } 413 | else if(i < 65) { rect[i] = new Rect2d(1,1,2,2); } 414 | } 415 | for(int i = 0; i < rect.length; i++){ 416 | rTree.add(rect[i]); 417 | } 418 | Stats stat = rTree.collectStats(); 419 | stat.print(System.out); 420 | for(int i = 0; i < 5; i++){ 421 | rTree.remove(rect[64]); 422 | } 423 | Stats stat2 = rTree.collectStats(); 424 | stat2.print(System.out); 425 | } 426 | 427 | @Test 428 | public void treeUpdateTest() { 429 | final RTree rTree = createRect2DTree(RTree.Split.QUADRATIC); 430 | 431 | Rect2d rect = new Rect2d(0, 1, 2, 3); 432 | rTree.add(rect); 433 | Rect2d oldRect = new Rect2d(0,1,2,3); 434 | Rect2d newRect = new Rect2d(1,2,3,4); 435 | rTree.update(oldRect, newRect); 436 | Rect2d[] results = new Rect2d[2]; 437 | final int num = rTree.search(newRect, results); 438 | Assert.assertTrue("Did not find the updated HyperRect", num == 1); 439 | System.out.print(results[0]); 440 | } 441 | 442 | @Test 443 | public void testAddsubtreeWithSideTree() { 444 | final RTree rTree = createRect2DTree(3, 6, RTree.Split.QUADRATIC); 445 | 446 | final Rect2d search; 447 | 448 | rTree.add(new Rect2d(2, 2, 4, 4)); 449 | rTree.add(search = new Rect2d(5, 2, 6, 3)); 450 | 451 | // now make sure root node is a branch 452 | for(int i=0; i<5; i++) { 453 | rTree.add(new Rect2d(3.0 - 1.0/(10.0+i),3.0 - 1.0/(10.0+i), 3.0 + 1.0/(10.0+i),3.0 + 1.0/(10.0+i))); 454 | } 455 | 456 | // add subtree/child on first rectangle - fully contained 457 | rTree.add(new Rect2d(2.5, 2.5, 3.5, 3.5)); 458 | 459 | Assert.assertEquals(8, rTree.getEntryCount()); 460 | 461 | final AtomicInteger hitCount = new AtomicInteger(); 462 | // but 5, 2, 6, 3 must still be found! 463 | rTree.search(search, (closure) -> { hitCount.incrementAndGet();}); 464 | 465 | Assert.assertEquals(1, hitCount.get()); 466 | 467 | } 468 | 469 | /** 470 | * Generate 'count' random rectangles with fixed ranges. 471 | * 472 | * @param count - number of rectangles to generate 473 | * @return array of generated rectangles 474 | */ 475 | @Ignore 476 | static Rect2d[] generateRandomRects(int count) { 477 | final Random rand = new Random(13); 478 | 479 | // changing these values changes the rectangle sizes and consequently the distribution density 480 | final int minX = 500; 481 | final int minY = 500; 482 | final int maxXRange = 25; 483 | final int maxYRange = 25; 484 | 485 | final double hitProb = 1.0 * count * maxXRange * maxYRange / (minX * minY); 486 | 487 | final Rect2d[] rects = new Rect2d[count]; 488 | for (int i = 0; i < count; i++) { 489 | final int x1 = rand.nextInt(minX); 490 | final int y1 = rand.nextInt(minY); 491 | final int x2 = x1 + rand.nextInt(maxXRange); 492 | final int y2 = y1 + rand.nextInt(maxYRange); 493 | rects[i] = new Rect2d(x1, y1, x2, y2); 494 | } 495 | 496 | return rects; 497 | } 498 | 499 | /** 500 | * Create a tree capable of holding rectangles with default minM (2) and maxM (8) values. 501 | * 502 | * @param splitType - type of leaf to use (affects how full nodes get split) 503 | * @return tree 504 | */ 505 | @Ignore 506 | static RTree createRect2DTree(RTree.Split splitType) { 507 | return createRect2DTree(2, 8, splitType); 508 | } 509 | 510 | /** 511 | * Create a tree capable of holding rectangles with specified m and M values. 512 | * 513 | * @param minM - minimum number of entries in each leaf 514 | * @param maxM - maximum number of entries in each leaf 515 | * @param splitType - type of leaf to use (affects how full nodes get split) 516 | * @return tree 517 | */ 518 | @Ignore 519 | static RTree createRect2DTree(int minM, int maxM, RTree.Split splitType) { 520 | return new RTree<>(new Rect2d.Builder(), minM, maxM, splitType); 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /src/test/java/com/conversantmedia/util/collection/spatial/Rect1DTest.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2018, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * John Cairns © 2018 9 | * ~~ 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | * #L% 22 | */ 23 | 24 | 25 | import com.conversantmedia.util.collection.geometry.Range1d; 26 | import org.junit.Assert; 27 | import org.junit.Test; 28 | 29 | /** 30 | * Created by jcairns on 7/18/18. 31 | */ 32 | public class Rect1DTest { 33 | 34 | @Test 35 | public void centroidTest() { 36 | Range1d rect = new Range1d(0D, 1D); 37 | Assert.assertTrue(RTree.isEqual(.5D, rect.getCentroid().getCoord(0))); 38 | } 39 | 40 | @Test 41 | public void mbrTest() { 42 | Range1d r1 = new Range1d(0, 5); 43 | Range1d r2 = new Range1d(4, 7); 44 | Range1d r3 = new Range1d(-4, 6); 45 | 46 | Range1d mbr = (Range1d)r1.getMbr(r2); 47 | mbr = (Range1d)mbr.getMbr(r3); 48 | 49 | Assert.assertTrue(RTree.isEqual(-4, mbr.getMin().getCoord(0))); 50 | Assert.assertTrue(RTree.isEqual(7, mbr.getMax().getCoord(0))); 51 | } 52 | 53 | @Test 54 | public void checkContains() { 55 | 56 | Range1d r1 = new Range1d(0, 5); 57 | 58 | Range1d r2 = new Range1d(2, 3); 59 | 60 | Range1d r3 = new Range1d(3, 10); 61 | 62 | Assert.assertTrue(r1.contains(r2)); 63 | Assert.assertFalse(r2.contains(r1)); 64 | Assert.assertFalse(r1.contains(r3)); 65 | 66 | } 67 | 68 | @Test 69 | public void checkIntersects() { 70 | 71 | Range1d r1 = new Range1d(0, 5); 72 | 73 | Range1d r2 = new Range1d(2, 3); 74 | 75 | Range1d r3 = new Range1d(3, 10); 76 | 77 | Assert.assertTrue(r1.intersects(r2)); 78 | Assert.assertTrue(r2.intersects(r1)); 79 | Assert.assertTrue(r1.intersects(r3)); 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/conversantmedia/util/collection/spatial/Rect2DTest.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.geometry.Rect2d; 24 | import org.junit.Test; 25 | import org.junit.Assert; 26 | 27 | /** 28 | * Created by jcovert on 6/16/15. 29 | */ 30 | public class Rect2DTest { 31 | 32 | @Test 33 | public void centroidTest() { 34 | 35 | Rect2d rect = new Rect2d(0, 0, 4, 3); 36 | 37 | HyperPoint centroid = rect.getCentroid(); 38 | double x = centroid.getCoord(0); 39 | double y = centroid.getCoord(1); 40 | Assert.assertTrue("Bad X-coord of centroid - expected " + 2.0 + " but was " + x, RTree.isEqual(x, 2.0d)); 41 | Assert.assertTrue("Bad Y-coord of centroid - expected " + 1.5 + " but was " + y, RTree.isEqual(y, 1.5d)); 42 | } 43 | 44 | @Test 45 | public void mbrTest() { 46 | 47 | Rect2d rect = new Rect2d(0, 0, 4, 3); 48 | 49 | // shouldn't effect MBR 50 | Rect2d rectInside = new Rect2d(0, 0, 1, 1); 51 | HyperRect mbr = rect.getMbr(rectInside); 52 | double expectedMinX = rect.getMin().getCoord(0); 53 | double expectedMinY = rect.getMin().getCoord(1); 54 | double expectedMaxX = rect.getMax().getCoord(0); 55 | double expectedMaxY = rect.getMax().getCoord(1); 56 | double actualMinX = mbr.getMin().getCoord(0); 57 | double actualMinY = mbr.getMin().getCoord(1); 58 | double actualMaxX = mbr.getMax().getCoord(0); 59 | double actualMaxY = mbr.getMax().getCoord(1); 60 | Assert.assertTrue("Bad minX - Expected: " + expectedMinX + " Actual: " + actualMinX, actualMinX == expectedMinX); 61 | Assert.assertTrue("Bad minY - Expected: " + expectedMinY + " Actual: " + actualMinY, actualMinY == expectedMinY); 62 | Assert.assertTrue("Bad maxX - Expected: " + expectedMaxX + " Actual: " + actualMaxX, actualMaxX == expectedMaxX); 63 | Assert.assertTrue("Bad maxY - Expected: " + expectedMaxY + " Actual: " + actualMaxY, actualMaxY == expectedMaxY); 64 | 65 | // should affect MBR 66 | Rect2d rectOverlap = new Rect2d(3, 1, 5, 4); 67 | mbr = rect.getMbr(rectOverlap); 68 | expectedMinX = 0.0d; 69 | expectedMinY = 0.0d; 70 | expectedMaxX = 5.0d; 71 | expectedMaxY = 4.0d; 72 | actualMinX = mbr.getMin().getCoord(0); 73 | actualMinY = mbr.getMin().getCoord(1); 74 | actualMaxX = mbr.getMax().getCoord(0); 75 | actualMaxY = mbr.getMax().getCoord(1); 76 | Assert.assertTrue("Bad minX - Expected: " + expectedMinX + " Actual: " + actualMinX, actualMinX == expectedMinX); 77 | Assert.assertTrue("Bad minY - Expected: " + expectedMinY + " Actual: " + actualMinY, actualMinY == expectedMinY); 78 | Assert.assertTrue("Bad maxX - Expected: " + expectedMaxX + " Actual: " + actualMaxX, actualMaxX == expectedMaxX); 79 | Assert.assertTrue("Bad maxY - Expected: " + expectedMaxY + " Actual: " + actualMaxY, actualMaxY == expectedMaxY); 80 | } 81 | 82 | @Test 83 | public void rangeTest() { 84 | 85 | Rect2d rect = new Rect2d(0, 0, 4, 3); 86 | 87 | double xRange = rect.getRange(0); 88 | double yRange = rect.getRange(1); 89 | Assert.assertTrue("Bad range in dimension X - expected " + 4.0 + " but was " + xRange, xRange == 4.0d); 90 | Assert.assertTrue("Bad range in dimension Y - expected " + 3.0 + " but was " + yRange, yRange == 3.0d); 91 | } 92 | 93 | 94 | @Test 95 | public void containsTest() { 96 | 97 | Rect2d rect = new Rect2d(0, 0, 4, 3); 98 | 99 | // shares an edge on the outside, not contained 100 | Rect2d rectOutsideNotContained = new Rect2d(4, 2, 5, 3); 101 | Assert.assertTrue("Shares an edge but should not be 'contained'", !rect.contains(rectOutsideNotContained)); 102 | 103 | // shares an edge on the inside, not contained 104 | Rect2d rectInsideNotContained = new Rect2d(0, 1, 4, 5); 105 | Assert.assertTrue("Shares an edge but should not be 'contained'", !rect.contains(rectInsideNotContained)); 106 | 107 | // shares an edge on the inside, contained 108 | Rect2d rectInsideContained = new Rect2d(0, 1, 1, 2); 109 | Assert.assertTrue("Shares an edge and should be 'contained'", rect.contains(rectInsideContained)); 110 | 111 | // intersects 112 | Rect2d rectIntersects = new Rect2d(3, 2, 5, 4); 113 | Assert.assertTrue("Intersects but should not be 'contained'", !rect.contains(rectIntersects)); 114 | 115 | // contains 116 | Rect2d rectContained = new Rect2d(1, 1, 2, 2); 117 | Assert.assertTrue("Contains and should be 'contained'", rect.contains(rectContained)); 118 | 119 | // does not contain or intersect 120 | Rect2d rectNotContained = new Rect2d(5, 0, 6, 1); 121 | Assert.assertTrue("Does not contain and should not be 'contained'", !rect.contains(rectNotContained)); 122 | } 123 | 124 | @Test 125 | public void intersectsTest() { 126 | 127 | Rect2d rect = new Rect2d(0, 0, 4, 3); 128 | 129 | // shares an edge on the outside, intersects 130 | Rect2d rectOutsideIntersects = new Rect2d(4, 2, 5, 3); 131 | Assert.assertTrue("Shares an edge and should 'intersect'", rect.intersects(rectOutsideIntersects)); 132 | 133 | // shares an edge on the inside, intersects 134 | Rect2d rectInsideIntersects = new Rect2d(0, 1, 4, 5); 135 | Assert.assertTrue("Shares an edge and should 'intersect'", rect.intersects(rectInsideIntersects)); 136 | 137 | // shares an edge on the inside, intersects 138 | Rect2d rectInsideIntersectsContained = new Rect2d(0, 1, 1, 2); 139 | Assert.assertTrue("Shares an edge and should 'intersect'", rect.intersects(rectInsideIntersectsContained)); 140 | 141 | // intersects 142 | Rect2d rectIntersects = new Rect2d(3, 2, 5, 4); 143 | Assert.assertTrue("Intersects and should 'intersect'", rect.intersects(rectIntersects)); 144 | 145 | // contains 146 | Rect2d rectContained = new Rect2d(1, 1, 2, 2); 147 | Assert.assertTrue("Contains and should 'intersect'", rect.intersects(rectContained)); 148 | 149 | // does not contain or intersect 150 | Rect2d rectNotIntersects = new Rect2d(5, 0, 6, 1); 151 | Assert.assertTrue("Does not intersect and should not 'intersect'", !rect.intersects(rectNotIntersects)); 152 | } 153 | 154 | @Test 155 | public void costTest() { 156 | 157 | Rect2d rect = new Rect2d(0, 0, 4, 3); 158 | double cost = rect.cost(); 159 | Assert.assertTrue("Bad cost - expected " + 12.0 + " but was " + cost, cost == 12.0d); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/test/java/com/conversantmedia/util/collection/spatial/Rect3DTest.java: -------------------------------------------------------------------------------- 1 | package com.conversantmedia.util.collection.spatial; 2 | 3 | /* 4 | * #%L 5 | * Conversant RTree 6 | * ~~ 7 | * Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc. 8 | * ~~ 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import com.conversantmedia.util.collection.geometry.Point3d; 24 | import com.conversantmedia.util.collection.geometry.Rect2d; 25 | import com.conversantmedia.util.collection.geometry.Rect3d; 26 | import org.junit.Assert; 27 | import org.junit.Test; 28 | 29 | /** 30 | * Created by jcovert on 6/16/15. 31 | */ 32 | public class Rect3DTest { 33 | 34 | @Test 35 | public void centroidTest() { 36 | 37 | Rect3d rect = new Rect3d(0, 0, 0, 4, 3, 2); 38 | 39 | HyperPoint centroid = rect.getCentroid(); 40 | double x = centroid.getCoord(Point3d.X); 41 | double y = centroid.getCoord(Point3d.Y); 42 | double z = centroid.getCoord(Point3d.Z); 43 | Assert.assertTrue("Bad X-coord of centroid - expected " + 2.0 + " but was " + x, RTree.isEqual(x, 2.0d)); 44 | Assert.assertTrue("Bad Y-coord of centroid - expected " + 1.5 + " but was " + y, RTree.isEqual(y, 1.5d)); 45 | Assert.assertTrue("Bad Z-coord of centroid - expected " + 1.0 + " but was " + y, RTree.isEqual(z, 1.0d)); 46 | } 47 | 48 | @Test 49 | public void mbrTest() { 50 | 51 | Rect3d rect = new Rect3d(0, 0, 0, 4, 3, 2); 52 | 53 | // shouldn't affect MBR 54 | Rect3d rectInside = new Rect3d(0, 0, 0, 1, 1, 1); 55 | HyperRect mbr = rect.getMbr(rectInside); 56 | double expectedMinX = rect.getMin().getCoord(Point3d.X); 57 | double expectedMinY = rect.getMin().getCoord(Point3d.Y); 58 | double expectedMinZ = rect.getMin().getCoord(Point3d.Z); 59 | double expectedMaxX = rect.getMax().getCoord(Point3d.X); 60 | double expectedMaxY = rect.getMax().getCoord(Point3d.Y); 61 | double expectedMaxZ = rect.getMin().getCoord(Point3d.Z); 62 | double actualMinX = mbr.getMin().getCoord(Point3d.X); 63 | double actualMinY = mbr.getMin().getCoord(Point3d.Y); 64 | double actualMinZ = mbr.getMin().getCoord(Point3d.Z); 65 | double actualMaxX = mbr.getMax().getCoord(Point3d.X); 66 | double actualMaxY = mbr.getMax().getCoord(Point3d.Y); 67 | double actualMaxZ = mbr.getMax().getCoord(Point3d.Z); 68 | Assert.assertTrue("Bad minX - Expected: " + expectedMinX + " Actual: " + actualMinX, RTree.isEqual(actualMinX, expectedMinX)); 69 | Assert.assertTrue("Bad minY - Expected: " + expectedMinY + " Actual: " + actualMinY, RTree.isEqual(actualMinY, expectedMinY)); 70 | Assert.assertTrue("Bad maxX - Expected: " + expectedMaxX + " Actual: " + actualMaxX, RTree.isEqual(actualMaxX, expectedMaxX)); 71 | Assert.assertTrue("Bad maxY - Expected: " + expectedMaxY + " Actual: " + actualMaxY, RTree.isEqual(actualMaxY, expectedMaxY)); 72 | 73 | // should affect MBR 74 | Rect3d rectOverlap = new Rect3d(3, 1, 3, 5, 4, 4); 75 | mbr = rect.getMbr(rectOverlap); 76 | expectedMinX = 0.0d; 77 | expectedMinY = 0.0d; 78 | expectedMinZ = 0.0d; 79 | expectedMaxX = 5.0d; 80 | expectedMaxY = 4.0d; 81 | expectedMaxZ = 4.0d; 82 | actualMinX = mbr.getMin().getCoord(Point3d.X); 83 | actualMinY = mbr.getMin().getCoord(Point3d.Y); 84 | actualMinZ = mbr.getMin().getCoord(Point3d.Z); 85 | actualMaxX = mbr.getMax().getCoord(Point3d.X); 86 | actualMaxY = mbr.getMax().getCoord(Point3d.Y); 87 | actualMaxZ = mbr.getMax().getCoord(Point3d.Z); 88 | Assert.assertTrue("Bad minX - Expected: " + expectedMinX + " Actual: " + actualMinX, RTree.isEqual(actualMinX, expectedMinX)); 89 | Assert.assertTrue("Bad minY - Expected: " + expectedMinY + " Actual: " + actualMinY, RTree.isEqual(actualMinY, expectedMinY)); 90 | Assert.assertTrue("Bad maxX - Expected: " + expectedMaxX + " Actual: " + actualMaxX, RTree.isEqual(actualMaxX, expectedMaxX)); 91 | Assert.assertTrue("Bad maxY - Expected: " + expectedMaxY + " Actual: " + actualMaxY, RTree.isEqual(actualMaxY, expectedMaxY)); 92 | } 93 | 94 | @Test 95 | public void rangeTest() { 96 | 97 | Rect3d rect = new Rect3d(0, 0, 0, 4, 3, 2); 98 | 99 | double xRange = rect.getRange(Point3d.X); 100 | double yRange = rect.getRange(Point3d.Y); 101 | double zRange = rect.getRange(Point3d.Z); 102 | Assert.assertTrue("Bad range in dimension X - expected " + 4.0 + " but was " + xRange, RTree.isEqual(xRange, 4.0d)); 103 | Assert.assertTrue("Bad range in dimension Y - expected " + 3.0 + " but was " + yRange, RTree.isEqual(yRange, 3.0d)); 104 | Assert.assertTrue("Bad range in dimension Y - expected " + 2.0 + " but was " + zRange, RTree.isEqual(zRange, 2.0d)); 105 | } 106 | 107 | 108 | @Test 109 | public void containsTest() { 110 | 111 | Rect3d rect = new Rect3d(0, 0, 0, 4, 3, 2); 112 | 113 | // shares an edge on the outside, not contained 114 | Rect3d rectOutsideNotContained = new Rect3d(4, 2, 4, 5, 3, 5); 115 | Assert.assertTrue("Shares an edge but should not be 'contained'", !rect.contains(rectOutsideNotContained)); 116 | 117 | // shares an edge on the inside, not contained 118 | Rect3d rectInsideNotContained = new Rect3d(0, 1, 0, 4, 5, 0); 119 | Assert.assertTrue("Shares an edge but should not be 'contained'", !rect.contains(rectInsideNotContained)); 120 | 121 | // shares an edge on the inside, contained 122 | Rect3d rectInsideContained = new Rect3d(0, 1, 0, 1, 2, 0); 123 | Assert.assertTrue("Shares an edge and should be 'contained'", rect.contains(rectInsideContained)); 124 | 125 | // intersects 126 | Rect3d rectIntersects = new Rect3d(3, 2, 0, 5, 4, 0); 127 | Assert.assertTrue("Intersects but should not be 'contained'", !rect.contains(rectIntersects)); 128 | 129 | // contains 130 | Rect3d rectContained = new Rect3d(1, 1, 1, 2, 2, 2); 131 | Assert.assertTrue("Contains and should be 'contained'", rect.contains(rectContained)); 132 | 133 | // does not contain or intersect 134 | Rect3d rectNotContained = new Rect3d(5, 0, 0, 6, 1, 0); 135 | Assert.assertTrue("Does not contain and should not be 'contained'", !rect.contains(rectNotContained)); 136 | } 137 | 138 | @Test 139 | public void intersectsTest() { 140 | 141 | Rect3d rect = new Rect3d(0, 0, 0, 4, 3, 0); 142 | 143 | // shares an edge on the outside, intersects 144 | Rect3d rectOutsideIntersects = new Rect3d(4, 2, 0, 5, 3, 0); 145 | Assert.assertTrue("Shares an edge and should 'intersect'", rect.intersects(rectOutsideIntersects)); 146 | 147 | // shares an edge on the inside, intersects 148 | Rect3d rectInsideIntersects = new Rect3d(0, 1, 0, 4, 5, 0); 149 | Assert.assertTrue("Shares an edge and should 'intersect'", rect.intersects(rectInsideIntersects)); 150 | 151 | // shares an edge on the inside, intersects 152 | Rect3d rectInsideIntersectsContained = new Rect3d(0, 1, 0, 1, 2, 0); 153 | Assert.assertTrue("Shares an edge and should 'intersect'", rect.intersects(rectInsideIntersectsContained)); 154 | 155 | // intersects 156 | Rect3d rectIntersects = new Rect3d(3, 2, 0, 5, 4, 0); 157 | Assert.assertTrue("Intersects and should 'intersect'", rect.intersects(rectIntersects)); 158 | 159 | // contains 160 | Rect3d rectContained = new Rect3d(1, 1, 0, 2, 2, 0); 161 | Assert.assertTrue("Contains and should 'intersect'", rect.intersects(rectContained)); 162 | 163 | // does not contain or intersect 164 | Rect3d rectNotIntersects = new Rect3d(5, 0, 0, 6, 1, 0); 165 | Assert.assertTrue("Does not intersect and should not 'intersect'", !rect.intersects(rectNotIntersects)); 166 | } 167 | 168 | @Test 169 | public void costTest() { 170 | 171 | Rect3d rect = new Rect3d(0, 0, 0, 4, 3, 2); 172 | double cost = rect.cost(); 173 | Assert.assertTrue("Bad cost - expected " + 24.0 + " but was " + cost, cost == 24D); 174 | } 175 | } 176 | --------------------------------------------------------------------------------