diff --git a/libs/h3/LICENSE.txt b/libs/h3/LICENSE.txt new file mode 100644 index 0000000000000..3ab280ebc84c7 --- /dev/null +++ b/libs/h3/LICENSE.txt @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. diff --git a/libs/h3/NOTICE.txt b/libs/h3/NOTICE.txt new file mode 100644 index 0000000000000..a8e5e1c94c87c --- /dev/null +++ b/libs/h3/NOTICE.txt @@ -0,0 +1,21 @@ +Elastic-hex + +Copyright 2022 Elasticsearch B.V. + +-- + +This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + +Copyright 2017-2021 Uber Technologies, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/libs/h3/build.gradle b/libs/h3/build.gradle new file mode 100644 index 0000000000000..3207450a7661b --- /dev/null +++ b/libs/h3/build.gradle @@ -0,0 +1,39 @@ +import org.elasticsearch.gradle.internal.conventions.precommit.LicenseHeadersTask + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +apply plugin: 'elasticsearch.build' +apply plugin: 'elasticsearch.publish' + +dependencies { + testImplementation(project(":test:framework")) { + exclude group: 'org.elasticsearch', module: 'elasticsearch-geo' + } +} + +tasks.named('forbiddenApisMain').configure { + // geo does not depend on server + // TODO: Need to decide how we want to handle for forbidden signatures with the changes to core + replaceSignatureFiles 'jdk-signatures' +} + +tasks.withType(LicenseHeadersTask.class).configureEach { + approvedLicenses = ['Apache'] +} + diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/BaseCells.java b/libs/h3/src/main/java/org/elasticsearch/h3/BaseCells.java new file mode 100644 index 0000000000000..bf8eaf88a27e8 --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/BaseCells.java @@ -0,0 +1,652 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2018 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** + * Base cell related lookup tables and access functions. + */ +final class BaseCells { + + private static class BaseCellData { + // "home" face and normalized ijk coordinates on that face + final int homeFace; + final int homeI; + final int homeJ; + final int homeK; + // is this base cell a pentagon? + final boolean isPentagon; + // if a pentagon, what are its two clockwise offset + final int[] cwOffsetPent; + + /// faces? + BaseCellData(int homeFace, int homeI, int homeJ, int homeK, boolean isPentagon, int[] cwOffsetPent) { + this.homeFace = homeFace; + this.homeI = homeI; + this.homeJ = homeJ; + this.homeK = homeK; + this.isPentagon = isPentagon; + this.cwOffsetPent = cwOffsetPent; + } + } + + /** + * Resolution 0 base cell data table. + *

+ * For each base cell, gives the "home" face and ijk+ coordinates on that face, + * whether or not the base cell is a pentagon. Additionally, if the base cell + * is a pentagon, the two cw offset rotation adjacent faces are given (-1 + * indicates that no cw offset rotation faces exist for this base cell). + */ + private static final BaseCellData[] baseCellData = new BaseCellData[] { + new BaseCellData(1, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 0 + new BaseCellData(2, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 1 + new BaseCellData(1, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 2 + new BaseCellData(2, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 3 + new BaseCellData(0, 2, 0, 0, true, new int[] { -1, -1 }), // base cell 4 + new BaseCellData(1, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 5 + new BaseCellData(1, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 6 + new BaseCellData(2, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 7 + new BaseCellData(0, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 8 + new BaseCellData(2, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 9 + new BaseCellData(1, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 10 + new BaseCellData(1, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 11 + new BaseCellData(3, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 12 + new BaseCellData(3, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 13 + new BaseCellData(11, 2, 0, 0, true, new int[] { 2, 6 }), // base cell 14 + new BaseCellData(4, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 15 + new BaseCellData(0, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 16 + new BaseCellData(6, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 17 + new BaseCellData(0, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 18 + new BaseCellData(2, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 19 + new BaseCellData(7, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 20 + new BaseCellData(2, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 21 + new BaseCellData(0, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 22 + new BaseCellData(6, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 23 + new BaseCellData(10, 2, 0, 0, true, new int[] { 1, 5 }), // base cell 24 + new BaseCellData(6, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 25 + new BaseCellData(3, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 26 + new BaseCellData(11, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 27 + new BaseCellData(4, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 28 + new BaseCellData(3, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 29 + new BaseCellData(0, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 30 + new BaseCellData(4, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 31 + new BaseCellData(5, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 32 + new BaseCellData(0, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 33 + new BaseCellData(7, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 34 + new BaseCellData(11, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 35 + new BaseCellData(7, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 36 + new BaseCellData(10, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 37 + new BaseCellData(12, 2, 0, 0, true, new int[] { 3, 7 }), // base cell 38 + new BaseCellData(6, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 39 + new BaseCellData(7, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 40 + new BaseCellData(4, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 41 + new BaseCellData(3, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 42 + new BaseCellData(3, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 43 + new BaseCellData(4, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 44 + new BaseCellData(6, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 45 + new BaseCellData(11, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 46 + new BaseCellData(8, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 47 + new BaseCellData(5, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 48 + new BaseCellData(14, 2, 0, 0, true, new int[] { 0, 9 }), // base cell 49 + new BaseCellData(5, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 50 + new BaseCellData(12, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 51 + new BaseCellData(10, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 52 + new BaseCellData(4, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 53 + new BaseCellData(12, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 54 + new BaseCellData(7, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 55 + new BaseCellData(11, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 56 + new BaseCellData(10, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 57 + new BaseCellData(13, 2, 0, 0, true, new int[] { 4, 8 }), // base cell 58 + new BaseCellData(10, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 59 + new BaseCellData(11, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 60 + new BaseCellData(9, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 61 + new BaseCellData(8, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 62 + new BaseCellData(6, 2, 0, 0, true, new int[] { 11, 15 }), // base cell 63 + new BaseCellData(8, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 64 + new BaseCellData(9, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 65 + new BaseCellData(14, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 66 + new BaseCellData(5, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 67 + new BaseCellData(16, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 68 + new BaseCellData(8, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 69 + new BaseCellData(5, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 70 + new BaseCellData(12, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 71 + new BaseCellData(7, 2, 0, 0, true, new int[] { 12, 16 }), // base cell 72 + new BaseCellData(12, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 73 + new BaseCellData(10, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 74 + new BaseCellData(9, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 75 + new BaseCellData(13, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 76 + new BaseCellData(16, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 77 + new BaseCellData(15, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 78 + new BaseCellData(15, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 79 + new BaseCellData(16, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 80 + new BaseCellData(14, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 81 + new BaseCellData(13, 1, 1, 0, false, new int[] { 0, 0 }), // base cell 82 + new BaseCellData(5, 2, 0, 0, true, new int[] { 10, 19 }), // base cell 83 + new BaseCellData(8, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 84 + new BaseCellData(14, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 85 + new BaseCellData(9, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 86 + new BaseCellData(14, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 87 + new BaseCellData(17, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 88 + new BaseCellData(12, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 89 + new BaseCellData(16, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 90 + new BaseCellData(17, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 91 + new BaseCellData(15, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 92 + new BaseCellData(16, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 93 + new BaseCellData(9, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 94 + new BaseCellData(15, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 95 + new BaseCellData(13, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 96 + new BaseCellData(8, 2, 0, 0, true, new int[] { 13, 17 }), // base cell 97 + new BaseCellData(13, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 98 + new BaseCellData(17, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 99 + new BaseCellData(19, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 100 + new BaseCellData(14, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 101 + new BaseCellData(19, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 102 + new BaseCellData(17, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 103 + new BaseCellData(13, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 104 + new BaseCellData(17, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 105 + new BaseCellData(16, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 106 + new BaseCellData(9, 2, 0, 0, true, new int[] { 14, 18 }), // base cell 107 + new BaseCellData(15, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 108 + new BaseCellData(15, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 109 + new BaseCellData(18, 0, 1, 1, false, new int[] { 0, 0 }), // base cell 110 + new BaseCellData(18, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 111 + new BaseCellData(19, 0, 0, 1, false, new int[] { 0, 0 }), // base cell 112 + new BaseCellData(17, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 113 + new BaseCellData(19, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 114 + new BaseCellData(18, 0, 1, 0, false, new int[] { 0, 0 }), // base cell 115 + new BaseCellData(18, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 116 + new BaseCellData(19, 2, 0, 0, true, new int[] { -1, -1 }), // base cell 117 + new BaseCellData(19, 1, 0, 0, false, new int[] { 0, 0 }), // base cell 118 + new BaseCellData(18, 0, 0, 0, false, new int[] { 0, 0 }), // base cell 119 + new BaseCellData(19, 1, 0, 1, false, new int[] { 0, 0 }), // base cell 120 + new BaseCellData(18, 1, 0, 0, false, new int[] { 0, 0 }) // base cell 121 + }; + + /** + * base cell at a given ijk and required rotations into its system + */ + private static class BaseCellRotation { + final int baseCell; // base cell number + final int ccwRot60; // number of ccw 60 degree rotations relative to current + /// face + + BaseCellRotation(int baseCell, int ccwRot60) { + this.baseCell = baseCell; + this.ccwRot60 = ccwRot60; + } + } + + /** @brief Resolution 0 base cell lookup table for each face. + * + * Given the face number and a resolution 0 ijk+ coordinate in that face's + * face-centered ijk coordinate system, gives the base cell located at that + * coordinate and the number of 60 ccw rotations to rotate into that base + * cell's orientation. + * + * Valid lookup coordinates are from (0, 0, 0) to (2, 2, 2). + * + * This table can be accessed using the functions `_faceIjkToBaseCell` and + * `_faceIjkToBaseCellCCWrot60` + */ + private static final BaseCellRotation[][][][] faceIjkBaseCells = new BaseCellRotation[][][][] { + {// face 0 + { + // i 0 + { new BaseCellRotation(16, 0), new BaseCellRotation(18, 0), new BaseCellRotation(24, 0) }, // j 0 + { new BaseCellRotation(33, 0), new BaseCellRotation(30, 0), new BaseCellRotation(32, 3) }, // j 1 + { new BaseCellRotation(49, 1), new BaseCellRotation(48, 3), new BaseCellRotation(50, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(8, 0), new BaseCellRotation(5, 5), new BaseCellRotation(10, 5) }, // j 0 + { new BaseCellRotation(22, 0), new BaseCellRotation(16, 0), new BaseCellRotation(18, 0) }, // j 1 + { new BaseCellRotation(41, 1), new BaseCellRotation(33, 0), new BaseCellRotation(30, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(4, 0), new BaseCellRotation(0, 5), new BaseCellRotation(2, 5) }, // j 0 + { new BaseCellRotation(15, 1), new BaseCellRotation(8, 0), new BaseCellRotation(5, 5) }, // j 1 + { new BaseCellRotation(31, 1), new BaseCellRotation(22, 0), new BaseCellRotation(16, 0) } // j 2 + } }, + {// face 1 + { + // i 0 + { new BaseCellRotation(2, 0), new BaseCellRotation(6, 0), new BaseCellRotation(14, 0) }, // j 0 + { new BaseCellRotation(10, 0), new BaseCellRotation(11, 0), new BaseCellRotation(17, 3) }, // j 1 + { new BaseCellRotation(24, 1), new BaseCellRotation(23, 3), new BaseCellRotation(25, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(0, 0), new BaseCellRotation(1, 5), new BaseCellRotation(9, 5) }, // j 0 + { new BaseCellRotation(5, 0), new BaseCellRotation(2, 0), new BaseCellRotation(6, 0) }, // j 1 + { new BaseCellRotation(18, 1), new BaseCellRotation(10, 0), new BaseCellRotation(11, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(4, 1), new BaseCellRotation(3, 5), new BaseCellRotation(7, 5) }, // j 0 + { new BaseCellRotation(8, 1), new BaseCellRotation(0, 0), new BaseCellRotation(1, 5) }, // j 1 + { new BaseCellRotation(16, 1), new BaseCellRotation(5, 0), new BaseCellRotation(2, 0) } // j 2 + } }, + {// face 2 + { + // i 0 + { new BaseCellRotation(7, 0), new BaseCellRotation(21, 0), new BaseCellRotation(38, 0) }, // j 0 + { new BaseCellRotation(9, 0), new BaseCellRotation(19, 0), new BaseCellRotation(34, 3) }, // j 1 + { new BaseCellRotation(14, 1), new BaseCellRotation(20, 3), new BaseCellRotation(36, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(3, 0), new BaseCellRotation(13, 5), new BaseCellRotation(29, 5) }, // j 0 + { new BaseCellRotation(1, 0), new BaseCellRotation(7, 0), new BaseCellRotation(21, 0) }, // j 1 + { new BaseCellRotation(6, 1), new BaseCellRotation(9, 0), new BaseCellRotation(19, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(4, 2), new BaseCellRotation(12, 5), new BaseCellRotation(26, 5) }, // j 0 + { new BaseCellRotation(0, 1), new BaseCellRotation(3, 0), new BaseCellRotation(13, 5) }, // j 1 + { new BaseCellRotation(2, 1), new BaseCellRotation(1, 0), new BaseCellRotation(7, 0) } // j 2 + } }, + {// face 3 + { + // i 0 + { new BaseCellRotation(26, 0), new BaseCellRotation(42, 0), new BaseCellRotation(58, 0) }, // j 0 + { new BaseCellRotation(29, 0), new BaseCellRotation(43, 0), new BaseCellRotation(62, 3) }, // j 1 + { new BaseCellRotation(38, 1), new BaseCellRotation(47, 3), new BaseCellRotation(64, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(12, 0), new BaseCellRotation(28, 5), new BaseCellRotation(44, 5) }, // j 0 + { new BaseCellRotation(13, 0), new BaseCellRotation(26, 0), new BaseCellRotation(42, 0) }, // j 1 + { new BaseCellRotation(21, 1), new BaseCellRotation(29, 0), new BaseCellRotation(43, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(4, 3), new BaseCellRotation(15, 5), new BaseCellRotation(31, 5) }, // j 0 + { new BaseCellRotation(3, 1), new BaseCellRotation(12, 0), new BaseCellRotation(28, 5) }, // j 1 + { new BaseCellRotation(7, 1), new BaseCellRotation(13, 0), new BaseCellRotation(26, 0) } // j 2 + } }, + {// face 4 + { + // i 0 + { new BaseCellRotation(31, 0), new BaseCellRotation(41, 0), new BaseCellRotation(49, 0) }, // j 0 + { new BaseCellRotation(44, 0), new BaseCellRotation(53, 0), new BaseCellRotation(61, 3) }, // j 1 + { new BaseCellRotation(58, 1), new BaseCellRotation(65, 3), new BaseCellRotation(75, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(15, 0), new BaseCellRotation(22, 5), new BaseCellRotation(33, 5) }, // j 0 + { new BaseCellRotation(28, 0), new BaseCellRotation(31, 0), new BaseCellRotation(41, 0) }, // j 1 + { new BaseCellRotation(42, 1), new BaseCellRotation(44, 0), new BaseCellRotation(53, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(4, 4), new BaseCellRotation(8, 5), new BaseCellRotation(16, 5) }, // j 0 + { new BaseCellRotation(12, 1), new BaseCellRotation(15, 0), new BaseCellRotation(22, 5) }, // j 1 + { new BaseCellRotation(26, 1), new BaseCellRotation(28, 0), new BaseCellRotation(31, 0) } // j 2 + } }, + {// face 5 + { + // i 0 + { new BaseCellRotation(50, 0), new BaseCellRotation(48, 0), new BaseCellRotation(49, 3) }, // j 0 + { new BaseCellRotation(32, 0), new BaseCellRotation(30, 3), new BaseCellRotation(33, 3) }, // j 1 + { new BaseCellRotation(24, 3), new BaseCellRotation(18, 3), new BaseCellRotation(16, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(70, 0), new BaseCellRotation(67, 0), new BaseCellRotation(66, 3) }, // j 0 + { new BaseCellRotation(52, 3), new BaseCellRotation(50, 0), new BaseCellRotation(48, 0) }, // j 1 + { new BaseCellRotation(37, 3), new BaseCellRotation(32, 0), new BaseCellRotation(30, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(83, 0), new BaseCellRotation(87, 3), new BaseCellRotation(85, 3) }, // j 0 + { new BaseCellRotation(74, 3), new BaseCellRotation(70, 0), new BaseCellRotation(67, 0) }, // j 1 + { new BaseCellRotation(57, 1), new BaseCellRotation(52, 3), new BaseCellRotation(50, 0) } // j 2 + } }, + {// face 6 + { + // i 0 + { new BaseCellRotation(25, 0), new BaseCellRotation(23, 0), new BaseCellRotation(24, 3) }, // j 0 + { new BaseCellRotation(17, 0), new BaseCellRotation(11, 3), new BaseCellRotation(10, 3) }, // j 1 + { new BaseCellRotation(14, 3), new BaseCellRotation(6, 3), new BaseCellRotation(2, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(45, 0), new BaseCellRotation(39, 0), new BaseCellRotation(37, 3) }, // j 0 + { new BaseCellRotation(35, 3), new BaseCellRotation(25, 0), new BaseCellRotation(23, 0) }, // j 1 + { new BaseCellRotation(27, 3), new BaseCellRotation(17, 0), new BaseCellRotation(11, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(63, 0), new BaseCellRotation(59, 3), new BaseCellRotation(57, 3) }, // j 0 + { new BaseCellRotation(56, 3), new BaseCellRotation(45, 0), new BaseCellRotation(39, 0) }, // j 1 + { new BaseCellRotation(46, 3), new BaseCellRotation(35, 3), new BaseCellRotation(25, 0) } // j 2 + } }, + {// face 7 + { + // i 0 + { new BaseCellRotation(36, 0), new BaseCellRotation(20, 0), new BaseCellRotation(14, 3) }, // j 0 + { new BaseCellRotation(34, 0), new BaseCellRotation(19, 3), new BaseCellRotation(9, 3) }, // j 1 + { new BaseCellRotation(38, 3), new BaseCellRotation(21, 3), new BaseCellRotation(7, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(55, 0), new BaseCellRotation(40, 0), new BaseCellRotation(27, 3) }, // j 0 + { new BaseCellRotation(54, 3), new BaseCellRotation(36, 0), new BaseCellRotation(20, 0) }, // j 1 + { new BaseCellRotation(51, 3), new BaseCellRotation(34, 0), new BaseCellRotation(19, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(72, 0), new BaseCellRotation(60, 3), new BaseCellRotation(46, 3) }, // j 0 + { new BaseCellRotation(73, 3), new BaseCellRotation(55, 0), new BaseCellRotation(40, 0) }, // j 1 + { new BaseCellRotation(71, 3), new BaseCellRotation(54, 3), new BaseCellRotation(36, 0) } // j 2 + } }, + {// face 8 + { + // i 0 + { new BaseCellRotation(64, 0), new BaseCellRotation(47, 0), new BaseCellRotation(38, 3) }, // j 0 + { new BaseCellRotation(62, 0), new BaseCellRotation(43, 3), new BaseCellRotation(29, 3) }, // j 1 + { new BaseCellRotation(58, 3), new BaseCellRotation(42, 3), new BaseCellRotation(26, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(84, 0), new BaseCellRotation(69, 0), new BaseCellRotation(51, 3) }, // j 0 + { new BaseCellRotation(82, 3), new BaseCellRotation(64, 0), new BaseCellRotation(47, 0) }, // j 1 + { new BaseCellRotation(76, 3), new BaseCellRotation(62, 0), new BaseCellRotation(43, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(97, 0), new BaseCellRotation(89, 3), new BaseCellRotation(71, 3) }, // j 0 + { new BaseCellRotation(98, 3), new BaseCellRotation(84, 0), new BaseCellRotation(69, 0) }, // j 1 + { new BaseCellRotation(96, 3), new BaseCellRotation(82, 3), new BaseCellRotation(64, 0) } // j 2 + } }, + {// face 9 + { + // i 0 + { new BaseCellRotation(75, 0), new BaseCellRotation(65, 0), new BaseCellRotation(58, 3) }, // j 0 + { new BaseCellRotation(61, 0), new BaseCellRotation(53, 3), new BaseCellRotation(44, 3) }, // j 1 + { new BaseCellRotation(49, 3), new BaseCellRotation(41, 3), new BaseCellRotation(31, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(94, 0), new BaseCellRotation(86, 0), new BaseCellRotation(76, 3) }, // j 0 + { new BaseCellRotation(81, 3), new BaseCellRotation(75, 0), new BaseCellRotation(65, 0) }, // j 1 + { new BaseCellRotation(66, 3), new BaseCellRotation(61, 0), new BaseCellRotation(53, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(107, 0), new BaseCellRotation(104, 3), new BaseCellRotation(96, 3) }, // j 0 + { new BaseCellRotation(101, 3), new BaseCellRotation(94, 0), new BaseCellRotation(86, 0) }, // j 1 + { new BaseCellRotation(85, 3), new BaseCellRotation(81, 3), new BaseCellRotation(75, 0) } // j 2 + } }, + {// face 10 + { + // i 0 + { new BaseCellRotation(57, 0), new BaseCellRotation(59, 0), new BaseCellRotation(63, 3) }, // j 0 + { new BaseCellRotation(74, 0), new BaseCellRotation(78, 3), new BaseCellRotation(79, 3) }, // j 1 + { new BaseCellRotation(83, 3), new BaseCellRotation(92, 3), new BaseCellRotation(95, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(37, 0), new BaseCellRotation(39, 3), new BaseCellRotation(45, 3) }, // j 0 + { new BaseCellRotation(52, 0), new BaseCellRotation(57, 0), new BaseCellRotation(59, 0) }, // j 1 + { new BaseCellRotation(70, 3), new BaseCellRotation(74, 0), new BaseCellRotation(78, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(24, 0), new BaseCellRotation(23, 3), new BaseCellRotation(25, 3) }, // j 0 + { new BaseCellRotation(32, 3), new BaseCellRotation(37, 0), new BaseCellRotation(39, 3) }, // j 1 + { new BaseCellRotation(50, 3), new BaseCellRotation(52, 0), new BaseCellRotation(57, 0) } // j 2 + } }, + {// face 11 + { + // i 0 + { new BaseCellRotation(46, 0), new BaseCellRotation(60, 0), new BaseCellRotation(72, 3) }, // j 0 + { new BaseCellRotation(56, 0), new BaseCellRotation(68, 3), new BaseCellRotation(80, 3) }, // j 1 + { new BaseCellRotation(63, 3), new BaseCellRotation(77, 3), new BaseCellRotation(90, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(27, 0), new BaseCellRotation(40, 3), new BaseCellRotation(55, 3) }, // j 0 + { new BaseCellRotation(35, 0), new BaseCellRotation(46, 0), new BaseCellRotation(60, 0) }, // j 1 + { new BaseCellRotation(45, 3), new BaseCellRotation(56, 0), new BaseCellRotation(68, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(14, 0), new BaseCellRotation(20, 3), new BaseCellRotation(36, 3) }, // j 0 + { new BaseCellRotation(17, 3), new BaseCellRotation(27, 0), new BaseCellRotation(40, 3) }, // j 1 + { new BaseCellRotation(25, 3), new BaseCellRotation(35, 0), new BaseCellRotation(46, 0) } // j 2 + } }, + {// face 12 + { + // i 0 + { new BaseCellRotation(71, 0), new BaseCellRotation(89, 0), new BaseCellRotation(97, 3) }, // j 0 + { new BaseCellRotation(73, 0), new BaseCellRotation(91, 3), new BaseCellRotation(103, 3) }, // j 1 + { new BaseCellRotation(72, 3), new BaseCellRotation(88, 3), new BaseCellRotation(105, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(51, 0), new BaseCellRotation(69, 3), new BaseCellRotation(84, 3) }, // j 0 + { new BaseCellRotation(54, 0), new BaseCellRotation(71, 0), new BaseCellRotation(89, 0) }, // j 1 + { new BaseCellRotation(55, 3), new BaseCellRotation(73, 0), new BaseCellRotation(91, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(38, 0), new BaseCellRotation(47, 3), new BaseCellRotation(64, 3) }, // j 0 + { new BaseCellRotation(34, 3), new BaseCellRotation(51, 0), new BaseCellRotation(69, 3) }, // j 1 + { new BaseCellRotation(36, 3), new BaseCellRotation(54, 0), new BaseCellRotation(71, 0) } // j 2 + } }, + {// face 13 + { + // i 0 + { new BaseCellRotation(96, 0), new BaseCellRotation(104, 0), new BaseCellRotation(107, 3) }, // j 0 + { new BaseCellRotation(98, 0), new BaseCellRotation(110, 3), new BaseCellRotation(115, 3) }, // j 1 + { new BaseCellRotation(97, 3), new BaseCellRotation(111, 3), new BaseCellRotation(119, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(76, 0), new BaseCellRotation(86, 3), new BaseCellRotation(94, 3) }, // j 0 + { new BaseCellRotation(82, 0), new BaseCellRotation(96, 0), new BaseCellRotation(104, 0) }, // j 1 + { new BaseCellRotation(84, 3), new BaseCellRotation(98, 0), new BaseCellRotation(110, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(58, 0), new BaseCellRotation(65, 3), new BaseCellRotation(75, 3) }, // j 0 + { new BaseCellRotation(62, 3), new BaseCellRotation(76, 0), new BaseCellRotation(86, 3) }, // j 1 + { new BaseCellRotation(64, 3), new BaseCellRotation(82, 0), new BaseCellRotation(96, 0) } // j 2 + } }, + {// face 14 + { + // i 0 + { new BaseCellRotation(85, 0), new BaseCellRotation(87, 0), new BaseCellRotation(83, 3) }, // j 0 + { new BaseCellRotation(101, 0), new BaseCellRotation(102, 3), new BaseCellRotation(100, 3) }, // j 1 + { new BaseCellRotation(107, 3), new BaseCellRotation(112, 3), new BaseCellRotation(114, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(66, 0), new BaseCellRotation(67, 3), new BaseCellRotation(70, 3) }, // j 0 + { new BaseCellRotation(81, 0), new BaseCellRotation(85, 0), new BaseCellRotation(87, 0) }, // j 1 + { new BaseCellRotation(94, 3), new BaseCellRotation(101, 0), new BaseCellRotation(102, 3) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(49, 0), new BaseCellRotation(48, 3), new BaseCellRotation(50, 3) }, // j 0 + { new BaseCellRotation(61, 3), new BaseCellRotation(66, 0), new BaseCellRotation(67, 3) }, // j 1 + { new BaseCellRotation(75, 3), new BaseCellRotation(81, 0), new BaseCellRotation(85, 0) } // j 2 + } }, + {// face 15 + { + // i 0 + { new BaseCellRotation(95, 0), new BaseCellRotation(92, 0), new BaseCellRotation(83, 0) }, // j 0 + { new BaseCellRotation(79, 0), new BaseCellRotation(78, 0), new BaseCellRotation(74, 3) }, // j 1 + { new BaseCellRotation(63, 1), new BaseCellRotation(59, 3), new BaseCellRotation(57, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(109, 0), new BaseCellRotation(108, 0), new BaseCellRotation(100, 5) }, // j 0 + { new BaseCellRotation(93, 1), new BaseCellRotation(95, 0), new BaseCellRotation(92, 0) }, // j 1 + { new BaseCellRotation(77, 1), new BaseCellRotation(79, 0), new BaseCellRotation(78, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(117, 4), new BaseCellRotation(118, 5), new BaseCellRotation(114, 5) }, // j 0 + { new BaseCellRotation(106, 1), new BaseCellRotation(109, 0), new BaseCellRotation(108, 0) }, // j 1 + { new BaseCellRotation(90, 1), new BaseCellRotation(93, 1), new BaseCellRotation(95, 0) } // j 2 + } }, + {// face 16 + { + // i 0 + { new BaseCellRotation(90, 0), new BaseCellRotation(77, 0), new BaseCellRotation(63, 0) }, // j 0 + { new BaseCellRotation(80, 0), new BaseCellRotation(68, 0), new BaseCellRotation(56, 3) }, // j 1 + { new BaseCellRotation(72, 1), new BaseCellRotation(60, 3), new BaseCellRotation(46, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(106, 0), new BaseCellRotation(93, 0), new BaseCellRotation(79, 5) }, // j 0 + { new BaseCellRotation(99, 1), new BaseCellRotation(90, 0), new BaseCellRotation(77, 0) }, // j 1 + { new BaseCellRotation(88, 1), new BaseCellRotation(80, 0), new BaseCellRotation(68, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(117, 3), new BaseCellRotation(109, 5), new BaseCellRotation(95, 5) }, // j 0 + { new BaseCellRotation(113, 1), new BaseCellRotation(106, 0), new BaseCellRotation(93, 0) }, // j 1 + { new BaseCellRotation(105, 1), new BaseCellRotation(99, 1), new BaseCellRotation(90, 0) } // j 2 + } }, + {// face 17 + { + // i 0 + { new BaseCellRotation(105, 0), new BaseCellRotation(88, 0), new BaseCellRotation(72, 0) }, // j 0 + { new BaseCellRotation(103, 0), new BaseCellRotation(91, 0), new BaseCellRotation(73, 3) }, // j 1 + { new BaseCellRotation(97, 1), new BaseCellRotation(89, 3), new BaseCellRotation(71, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(113, 0), new BaseCellRotation(99, 0), new BaseCellRotation(80, 5) }, // j 0 + { new BaseCellRotation(116, 1), new BaseCellRotation(105, 0), new BaseCellRotation(88, 0) }, // j 1 + { new BaseCellRotation(111, 1), new BaseCellRotation(103, 0), new BaseCellRotation(91, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(117, 2), new BaseCellRotation(106, 5), new BaseCellRotation(90, 5) }, // j 0 + { new BaseCellRotation(121, 1), new BaseCellRotation(113, 0), new BaseCellRotation(99, 0) }, // j 1 + { new BaseCellRotation(119, 1), new BaseCellRotation(116, 1), new BaseCellRotation(105, 0) } // j 2 + } }, + {// face 18 + { + // i 0 + { new BaseCellRotation(119, 0), new BaseCellRotation(111, 0), new BaseCellRotation(97, 0) }, // j 0 + { new BaseCellRotation(115, 0), new BaseCellRotation(110, 0), new BaseCellRotation(98, 3) }, // j 1 + { new BaseCellRotation(107, 1), new BaseCellRotation(104, 3), new BaseCellRotation(96, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(121, 0), new BaseCellRotation(116, 0), new BaseCellRotation(103, 5) }, // j 0 + { new BaseCellRotation(120, 1), new BaseCellRotation(119, 0), new BaseCellRotation(111, 0) }, // j 1 + { new BaseCellRotation(112, 1), new BaseCellRotation(115, 0), new BaseCellRotation(110, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(117, 1), new BaseCellRotation(113, 5), new BaseCellRotation(105, 5) }, // j 0 + { new BaseCellRotation(118, 1), new BaseCellRotation(121, 0), new BaseCellRotation(116, 0) }, // j 1 + { new BaseCellRotation(114, 1), new BaseCellRotation(120, 1), new BaseCellRotation(119, 0) } // j 2 + } }, + {// face 19 + { + // i 0 + { new BaseCellRotation(114, 0), new BaseCellRotation(112, 0), new BaseCellRotation(107, 0) }, // j 0 + { new BaseCellRotation(100, 0), new BaseCellRotation(102, 0), new BaseCellRotation(101, 3) }, // j 1 + { new BaseCellRotation(83, 1), new BaseCellRotation(87, 3), new BaseCellRotation(85, 3) } // j 2 + }, + { + // i 1 + { new BaseCellRotation(118, 0), new BaseCellRotation(120, 0), new BaseCellRotation(115, 5) }, // j 0 + { new BaseCellRotation(108, 1), new BaseCellRotation(114, 0), new BaseCellRotation(112, 0) }, // j 1 + { new BaseCellRotation(92, 1), new BaseCellRotation(100, 0), new BaseCellRotation(102, 0) } // j 2 + }, + { + // i 2 + { new BaseCellRotation(117, 0), new BaseCellRotation(121, 5), new BaseCellRotation(119, 5) }, // j 0 + { new BaseCellRotation(109, 1), new BaseCellRotation(118, 0), new BaseCellRotation(120, 0) }, // j 1 + { new BaseCellRotation(95, 1), new BaseCellRotation(108, 1), new BaseCellRotation(114, 0) } // j 2 + } } }; + + /** + * Return whether or not the indicated base cell is a pentagon. + */ + public static boolean isBaseCellPentagon(int baseCell) { + if (baseCell < 0 || baseCell >= Constants.NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + return false; + } + return baseCellData[baseCell].isPentagon; + } + + /** + * Return whether or not the indicated base cell is a pentagon. + */ + public static FaceIJK getBaseFaceIJK(int baseCell) { + if (baseCell < 0 || baseCell >= Constants.NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + throw new IllegalArgumentException("Illegal base cell"); + } + BaseCellData cellData = baseCellData[baseCell]; + return new FaceIJK(cellData.homeFace, new CoordIJK(cellData.homeI, cellData.homeJ, cellData.homeK)); + } + + /** Find base cell given FaceIJK. + * + * Given the face number and a resolution 0 ijk+ coordinate in that face's + * face-centered ijk coordinate system, return the base cell located at that + * coordinate. + * + * Valid ijk+ lookup coordinates are from (0, 0, 0) to (2, 2, 2). + */ + public static int getBaseCell(FaceIJK faceIJK) { + return faceIjkBaseCells[faceIJK.face][faceIJK.coord.i][faceIJK.coord.j][faceIJK.coord.k].baseCell; + } + + /** Find base cell given FaceIJK. + * + * Given the face number and a resolution 0 ijk+ coordinate in that face's + * face-centered ijk coordinate system, return the number of 60' ccw rotations + * to rotate into the coordinate system of the base cell at that coordinates. + * + * Valid ijk+ lookup coordinates are from (0, 0, 0) to (2, 2, 2). + */ + public static int getBaseCellCCWrot60(FaceIJK faceIJK) { + return faceIjkBaseCells[faceIJK.face][faceIJK.coord.i][faceIJK.coord.j][faceIJK.coord.k].ccwRot60; + } + + /** Return whether or not the tested face is a cw offset face. + */ + public static boolean baseCellIsCwOffset(int baseCell, int testFace) { + return baseCellData[baseCell].cwOffsetPent[0] == testFace || baseCellData[baseCell].cwOffsetPent[1] == testFace; + } + + /** Return whether the indicated base cell is a pentagon where all + * neighbors are oriented towards it. */ + public static boolean isBaseCellPolarPentagon(int baseCell) { + return baseCell == 4 || baseCell == 117; + } + +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/CellBoundary.java b/libs/h3/src/main/java/org/elasticsearch/h3/CellBoundary.java new file mode 100644 index 0000000000000..74115d5a002d6 --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/CellBoundary.java @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2021 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** + * cell boundary points as {@link LatLng} + */ +public final class CellBoundary { + + /** Maximum number of cell boundary vertices; worst case is pentagon: + * 5 original verts + 5 edge crossings + */ + private static final int MAX_CELL_BNDRY_VERTS = 10; + /** How many points it holds */ + private int numVertext; + /** The actual points */ + private final LatLng[] points = new LatLng[MAX_CELL_BNDRY_VERTS]; + + CellBoundary() {} + + void add(LatLng point) { + points[numVertext++] = point; + } + + /** Number of points in this boundary */ + public int numPoints() { + return numVertext; + } + + /** Return the point at the given position*/ + public LatLng getLatLon(int i) { + if (i >= numVertext) { + throw new IndexOutOfBoundsException(); + } + return points[i]; + } +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/Constants.java b/libs/h3/src/main/java/org/elasticsearch/h3/Constants.java new file mode 100644 index 0000000000000..e4aa9876ddfa2 --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/Constants.java @@ -0,0 +1,72 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2017, 2020 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** + * Constants used by more than one source code file. + */ +final class Constants { + /** + * sqrt(3) / 2.0 + */ + public static double M_SQRT3_2 = 0.8660254037844386467637231707529361834714; + /** + * max H3 resolution; H3 version 1 has 16 resolutions, numbered 0 through 15 + */ + public static int MAX_H3_RES = 15; + /** + * The number of H3 base cells + */ + public static int NUM_BASE_CELLS = 122; + /** + * The number of vertices in a hexagon + */ + public static int NUM_HEX_VERTS = 6; + /** + * The number of vertices in a pentagon + */ + public static int NUM_PENT_VERTS = 5; + /** + * H3 index modes + */ + public static int H3_CELL_MODE = 1; + /** + * square root of 7 + */ + public static final double M_SQRT7 = 2.6457513110645905905016157536392604257102; + /** + * scaling factor from hex2d resolution 0 unit length + * (or distance between adjacent cell center points + * on the plane) to gnomonic unit length. + */ + public static double RES0_U_GNOMONIC = 0.38196601125010500003; + /** + * rotation angle between Class II and Class III resolution axes + * (asin(sqrt(3.0 / 28.0))) + */ + public static double M_AP7_ROT_RADS = 0.333473172251832115336090755351601070065900389; + /** + * threshold epsilon + */ + public static double EPSILON = 0.0000000000000001; +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/CoordIJK.java b/libs/h3/src/main/java/org/elasticsearch/h3/CoordIJK.java new file mode 100644 index 0000000000000..f1cf1b53e3acf --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/CoordIJK.java @@ -0,0 +1,394 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2018, 2020-2021 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** + * Mutable IJK hexagon coordinates + * + * Each axis is spaced 120 degrees apart. + * + * References two Vec2d cartesian coordinate systems: + * + * 1. gnomonic: face-centered polyhedral gnomonic projection space with + * traditional scaling and x-axes aligned with the face Class II + * i-axes. + * + * 2. hex2d: local face-centered coordinate system scaled a specific H3 grid + * resolution unit length and with x-axes aligned with the local + * i-axes + */ +final class CoordIJK { + + /** CoordIJK unit vectors corresponding to the 7 H3 digits. + */ + private static final int[][] UNIT_VECS = { + { 0, 0, 0 }, // direction 0 + { 0, 0, 1 }, // direction 1 + { 0, 1, 0 }, // direction 2 + { 0, 1, 1 }, // direction 3 + { 1, 0, 0 }, // direction 4 + { 1, 0, 1 }, // direction 5 + { 1, 1, 0 } // direction 6 + }; + + /** H3 digit representing ijk+ axes direction. + * Values will be within the lowest 3 bits of an integer. + */ + public enum Direction { + + CENTER_DIGIT(0), + K_AXES_DIGIT(1), + J_AXES_DIGIT(2), + JK_AXES_DIGIT(J_AXES_DIGIT.digit() | K_AXES_DIGIT.digit()), + I_AXES_DIGIT(4), + IK_AXES_DIGIT(I_AXES_DIGIT.digit() | K_AXES_DIGIT.digit()), + IJ_AXES_DIGIT(I_AXES_DIGIT.digit() | J_AXES_DIGIT.digit()), + INVALID_DIGIT(7), + NUM_DIGITS(INVALID_DIGIT.digit()), + PENTAGON_SKIPPED_DIGIT(K_AXES_DIGIT.digit()); + + Direction(int digit) { + this.digit = digit; + } + + private final int digit; + + public int digit() { + return digit; + } + + } + + int i; // i component + int j; // j component + int k; // k component + + CoordIJK(int i, int j, int k) { + this.i = i; + this.j = j; + this.k = k; + } + + /** + * Find the center point in 2D cartesian coordinates of a hex. + * + */ + public Vec2d ijkToHex2d() { + int i = this.i - this.k; + int j = this.j - this.k; + return new Vec2d(i - 0.5 * j, j * Constants.M_SQRT3_2); + } + + /** + * Add ijk coordinates. + * + * @param i the i coordinate + * @param j the j coordinate + * @param k the k coordinate + */ + + public void ijkAdd(int i, int j, int k) { + this.i += i; + this.j += j; + this.k += k; + } + + /** + * Subtract ijk coordinates. + * + * @param i the i coordinate + * @param j the j coordinate + * @param k the k coordinate + */ + public void ijkSub(int i, int j, int k) { + this.i -= i; + this.j -= j; + this.k -= k; + } + + /** + * Normalizes ijk coordinates by setting the ijk coordinates + * to the smallest possible values. + */ + public void ijkNormalize() { + // remove any negative values + if (i < 0) { + j -= i; + k -= i; + i = 0; + } + + if (j < 0) { + i -= j; + k -= j; + j = 0; + } + + if (k < 0) { + i -= k; + j -= k; + k = 0; + } + + // remove the min value if needed + int min = i; + if (j < min) { + min = j; + } + if (k < min) { + min = k; + } + if (min > 0) { + i -= min; + j -= min; + k -= min; + } + } + + /** + * Find the normalized ijk coordinates of the hex centered on the current + * hex at the next finer aperture 7 counter-clockwise resolution. + */ + public void downAp7() { + // res r unit vectors in res r+1 + // iVec (3, 0, 1) + // jVec (1, 3, 0) + // kVec (0, 1, 3) + final int i = this.i * 3 + this.j * 1 + this.k * 0; + final int j = this.i * 0 + this.j * 3 + this.k * 1; + final int k = this.i * 1 + this.j * 0 + this.k * 3; + this.i = i; + this.j = j; + this.k = k; + ijkNormalize(); + } + + /** + * Find the normalized ijk coordinates of the hex centered on the current + * hex at the next finer aperture 7 clockwise resolution. + */ + public void downAp7r() { + // iVec (3, 1, 0) + // jVec (0, 3, 1) + // kVec (1, 0, 3) + final int i = this.i * 3 + this.j * 0 + this.k * 1; + final int j = this.i * 1 + this.j * 3 + this.k * 0; + final int k = this.i * 0 + this.j * 1 + this.k * 3; + this.i = i; + this.j = j; + this.k = k; + ijkNormalize(); + } + + /** + * Find the normalized ijk coordinates of the hex centered on the current + * hex at the next finer aperture 3 counter-clockwise resolution. + */ + public void downAp3() { + // res r unit vectors in res r+1 + // iVec (2, 0, 1) + // jVec (1, 2, 0) + // kVec (0, 1, 2) + final int i = this.i * 2 + this.j * 1 + this.k * 0; + final int j = this.i * 0 + this.j * 2 + this.k * 1; + final int k = this.i * 1 + this.j * 0 + this.k * 2; + this.i = i; + this.j = j; + this.k = k; + ijkNormalize(); + } + + /** + * Find the normalized ijk coordinates of the hex centered on the current + * hex at the next finer aperture 3 clockwise resolution. + */ + public void downAp3r() { + // res r unit vectors in res r+1 + // iVec (2, 1, 0) + // jVec (0, 2, 1) + // kVec (1, 0, 2) + final int i = this.i * 2 + this.j * 0 + this.k * 1; + final int j = this.i * 1 + this.j * 2 + this.k * 0; + final int k = this.i * 0 + this.j * 1 + this.k * 2; + this.i = i; + this.j = j; + this.k = k; + ijkNormalize(); + } + + /** + * Rotates ijk coordinates 60 degrees clockwise. + * + */ + public void ijkRotate60cw() { + // unit vector rotations + // iVec (1, 0, 1) + // jVec (1, 1, 0) + // kVec (0, 1, 1) + final int i = this.i * 1 + this.j * 1 + this.k * 0; + final int j = this.i * 0 + this.j * 1 + this.k * 1; + final int k = this.i * 1 + this.j * 0 + this.k * 1; + this.i = i; + this.j = j; + this.k = k; + ijkNormalize(); + } + + /** + * Rotates ijk coordinates 60 degrees counter-clockwise. + */ + public void ijkRotate60ccw() { + // unit vector rotations + // iVec (1, 1, 0) + // jVec (0, 1, 1) + // kVec (1, 0, 1) + final int i = this.i * 1 + this.j * 0 + this.k * 1; + final int j = this.i * 1 + this.j * 1 + this.k * 0; + final int k = this.i * 0 + this.j * 1 + this.k * 1; + this.i = i; + this.j = j; + this.k = k; + ijkNormalize(); + } + + /** + * Find the normalized ijk coordinates of the hex in the specified digit + * direction from the current ijk coordinates. + * @param digit The digit direction from the original ijk coordinates. + */ + public void neighbor(int digit) { + if (digit > Direction.CENTER_DIGIT.digit() && digit < Direction.NUM_DIGITS.digit()) { + ijkAdd(UNIT_VECS[digit][0], UNIT_VECS[digit][1], UNIT_VECS[digit][2]); + ijkNormalize(); + } + } + + /** + * Find the normalized ijk coordinates of the indexing parent of a cell in a + * clockwise aperture 7 grid. + */ + public void upAp7r() { + i = this.i - this.k; + j = this.j - this.k; + int i = (int) Math.round((2 * this.i + this.j) / 7.0); + int j = (int) Math.round((3 * this.j - this.i) / 7.0); + this.i = i; + this.j = j; + this.k = 0; + ijkNormalize(); + } + + /** + * Find the normalized ijk coordinates of the indexing parent of a cell in a + * counter-clockwise aperture 7 grid. + * + */ + public void upAp7() { + i = this.i - this.k; + j = this.j - this.k; + int i = (int) Math.round((3 * this.i - this.j) / 7.0); + int j = (int) Math.round((this.i + 2 * this.j) / 7.0); + this.i = i; + this.j = j; + this.k = 0; + ijkNormalize(); + } + + /** + * Determines the H3 digit corresponding to a unit vector in ijk coordinates. + * + * @return The H3 digit (0-6) corresponding to the ijk unit vector, or + * INVALID_DIGIT on failure. + */ + public int unitIjkToDigit() { + ijkNormalize(); + int digit = Direction.INVALID_DIGIT.digit(); + for (int i = Direction.CENTER_DIGIT.digit(); i < Direction.NUM_DIGITS.digit(); i++) { + if (ijkMatches(UNIT_VECS[i])) { + digit = i; + break; + } + } + return digit; + } + + /** + * Returns whether or not two ijk coordinates contain exactly the same + * component values. + * + * @param c The set of ijk coordinates. + * @return true if the two addresses match, 0 if they do not. + */ + private boolean ijkMatches(int[] c) { + return (i == c[0] && j == c[1] && k == c[2]); + } + + /** + * Rotates indexing digit 60 degrees clockwise. Returns result. + * + * @param digit Indexing digit (between 1 and 6 inclusive) + */ + public static int rotate60cw(int digit) { + switch (digit) { + case 1: // K_AXES_DIGIT + return Direction.JK_AXES_DIGIT.digit(); + case 3: // JK_AXES_DIGIT: + return Direction.J_AXES_DIGIT.digit(); + case 2: // J_AXES_DIGIT: + return Direction.IJ_AXES_DIGIT.digit(); + case 6: // IJ_AXES_DIGIT + return Direction.I_AXES_DIGIT.digit(); + case 4: // I_AXES_DIGIT + return Direction.IK_AXES_DIGIT.digit(); + case 5: // IK_AXES_DIGIT + return Direction.K_AXES_DIGIT.digit(); + default: + return digit; + } + } + + /** + * Rotates indexing digit 60 degrees counter-clockwise. Returns result. + * + * @param digit Indexing digit (between 1 and 6 inclusive) + */ + public static int rotate60ccw(int digit) { + switch (digit) { + case 1: // K_AXES_DIGIT + return Direction.IK_AXES_DIGIT.digit(); + case 5: // IK_AXES_DIGIT + return Direction.I_AXES_DIGIT.digit(); + case 4: // I_AXES_DIGIT + return Direction.IJ_AXES_DIGIT.digit(); + case 6: // IJ_AXES_DIGIT + return Direction.J_AXES_DIGIT.digit(); + case 2: // J_AXES_DIGIT: + return Direction.JK_AXES_DIGIT.digit(); + case 3: // JK_AXES_DIGIT: + return Direction.K_AXES_DIGIT.digit(); + default: + return digit; + } + } + +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/FaceIJK.java b/libs/h3/src/main/java/org/elasticsearch/h3/FaceIJK.java new file mode 100644 index 0000000000000..9ae4b2333a1fe --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/FaceIJK.java @@ -0,0 +1,813 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2021 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** + * Mutable face number and ijk coordinates on that face-centered coordinate system. + * + * References the Vec2d cartesian coordinate systems hex2d: local face-centered + * coordinate system scaled a specific H3 grid resolution unit length and + * with x-axes aligned with the local i-axes + */ +final class FaceIJK { + + /** enum representing overage type */ + enum Overage { + /** + * Digit representing overage type + */ + NO_OVERAGE, + /** + * On face edge (only occurs on substrate grids) + */ + FACE_EDGE, + /** + * Overage on new face interior + */ + NEW_FACE + } + + // indexes for faceNeighbors table + /** + * IJ quadrant faceNeighbors table direction + */ + private static final int IJ = 1; + /** + * KI quadrant faceNeighbors table direction + */ + private static final int KI = 2; + /** + * JK quadrant faceNeighbors table direction + */ + private static final int JK = 3; + + /** + * overage distance table + */ + private static final int[] maxDimByCIIres = { + 2, // res 0 + -1, // res 1 + 14, // res 2 + -1, // res 3 + 98, // res 4 + -1, // res 5 + 686, // res 6 + -1, // res 7 + 4802, // res 8 + -1, // res 9 + 33614, // res 10 + -1, // res 11 + 235298, // res 12 + -1, // res 13 + 1647086, // res 14 + -1, // res 15 + 11529602 // res 16 + }; + + /** + * unit scale distance table + */ + private static final int[] unitScaleByCIIres = { + 1, // res 0 + -1, // res 1 + 7, // res 2 + -1, // res 3 + 49, // res 4 + -1, // res 5 + 343, // res 6 + -1, // res 7 + 2401, // res 8 + -1, // res 9 + 16807, // res 10 + -1, // res 11 + 117649, // res 12 + -1, // res 13 + 823543, // res 14 + -1, // res 15 + 5764801 // res 16 + }; + + /** + * direction from the origin face to the destination face, relative to + * the origin face's coordinate system, or -1 if not adjacent. + */ + private static final int[][] adjacentFaceDir = new int[][] { + { 0, KI, -1, -1, IJ, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // face 0 + { IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // face 1 + { -1, IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // face 2 + { -1, -1, IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // face 3 + { KI, -1, -1, IJ, 0, -1, -1, -1, -1, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // face 4 + { JK, -1, -1, -1, -1, 0, -1, -1, -1, -1, IJ, -1, -1, -1, KI, -1, -1, -1, -1, -1 }, // face 5 + { -1, JK, -1, -1, -1, -1, 0, -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1, -1, -1 }, // face 6 + { -1, -1, JK, -1, -1, -1, -1, 0, -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1, -1 }, // face 7 + { -1, -1, -1, JK, -1, -1, -1, -1, 0, -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1 }, // face 8 + { -1, -1, -1, -1, JK, -1, -1, -1, -1, 0, -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1 }, // face 9 + { -1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, 0, -1, -1, -1, -1, JK, -1, -1, -1, -1 }, // face 10 + { -1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, 0, -1, -1, -1, -1, JK, -1, -1, -1 }, // face 11 + { -1, -1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, 0, -1, -1, -1, -1, JK, -1, -1 }, // face 12 + { -1, -1, -1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, 0, -1, -1, -1, -1, JK, -1 }, // face 13 + { -1, -1, -1, -1, -1, KI, -1, -1, -1, IJ, -1, -1, -1, -1, 0, -1, -1, -1, -1, JK }, // face 14 + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, -1, -1, -1, -1, 0, IJ, -1, -1, KI }, // face 15 + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ, -1, -1 }, // face 16 + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ, -1 }, // face 17 + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ }, // face 18 + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, IJ, -1, -1, KI, 0 } // face 19 + }; + + /** Maximum input for any component to face-to-base-cell lookup functions */ + private static final int MAX_FACE_COORD = 2; + + /** + * Information to transform into an adjacent face IJK system + */ + private static class FaceOrientIJK { + // face number + final int face; + // res 0 translation relative to primary face + final int translateI; + final int translateJ; + final int translateK; + // number of 60 degree ccw rotations relative to primary + final int ccwRot60; + + // face + FaceOrientIJK(int face, int translateI, int translateJ, int translateK, int ccwRot60) { + this.face = face; + this.translateI = translateI; + this.translateJ = translateJ; + this.translateK = translateK; + this.ccwRot60 = ccwRot60; + } + } + + /** + * Definition of which faces neighbor each other. + */ + private static final FaceOrientIJK[][] faceNeighbors = new FaceOrientIJK[][] { + { + // face 0 + new FaceOrientIJK(0, 0, 0, 0, 0), // central face + new FaceOrientIJK(4, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(1, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(5, 0, 2, 2, 3) // jk quadrant + }, + { + // face 1 + new FaceOrientIJK(1, 0, 0, 0, 0), // central face + new FaceOrientIJK(0, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(2, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(6, 0, 2, 2, 3) // jk quadrant + }, + { + // face 2 + new FaceOrientIJK(2, 0, 0, 0, 0), // central face + new FaceOrientIJK(1, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(3, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(7, 0, 2, 2, 3) // jk quadrant + }, + { + // face 3 + new FaceOrientIJK(3, 0, 0, 0, 0), // central face + new FaceOrientIJK(2, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(4, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(8, 0, 2, 2, 3) // jk quadrant + }, + { + // face 4 + new FaceOrientIJK(4, 0, 0, 0, 0), // central face + new FaceOrientIJK(3, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(0, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(9, 0, 2, 2, 3) // jk quadrant + }, + { + // face 5 + new FaceOrientIJK(5, 0, 0, 0, 0), // central face + new FaceOrientIJK(10, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(14, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(0, 0, 2, 2, 3) // jk quadrant + }, + { + // face 6 + new FaceOrientIJK(6, 0, 0, 0, 0), // central face + new FaceOrientIJK(11, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(10, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(1, 0, 2, 2, 3) // jk quadrant + }, + { + // face 7 + new FaceOrientIJK(7, 0, 0, 0, 0), // central face + new FaceOrientIJK(12, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(11, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(2, 0, 2, 2, 3) // jk quadrant + }, + { + // face 8 + new FaceOrientIJK(8, 0, 0, 0, 0), // central face + new FaceOrientIJK(13, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(12, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(3, 0, 2, 2, 3) // jk quadrant + }, + { + // face 9 + new FaceOrientIJK(9, 0, 0, 0, 0), // central face + new FaceOrientIJK(14, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(13, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(4, 0, 2, 2, 3) // jk quadrant + }, + { + // face 10 + new FaceOrientIJK(10, 0, 0, 0, 0), // central face + new FaceOrientIJK(5, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(6, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(15, 0, 2, 2, 3) // jk quadrant + }, + { + // face 11 + new FaceOrientIJK(11, 0, 0, 0, 0), // central face + new FaceOrientIJK(6, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(7, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(16, 0, 2, 2, 3) // jk quadrant + }, + { + // face 12 + new FaceOrientIJK(12, 0, 0, 0, 0), // central face + new FaceOrientIJK(7, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(8, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(17, 0, 2, 2, 3) // jk quadrant + }, + { + // face 13 + new FaceOrientIJK(13, 0, 0, 0, 0), // central face + new FaceOrientIJK(8, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(9, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(18, 0, 2, 2, 3) // jk quadrant + }, + { + // face 14 + new FaceOrientIJK(14, 0, 0, 0, 0), // central face + new FaceOrientIJK(9, 2, 2, 0, 3), // ij quadrant + new FaceOrientIJK(5, 2, 0, 2, 3), // ki quadrant + new FaceOrientIJK(19, 0, 2, 2, 3) // jk quadrant + }, + { + // face 15 + new FaceOrientIJK(15, 0, 0, 0, 0), // central face + new FaceOrientIJK(16, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(19, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(10, 0, 2, 2, 3) // jk quadrant + }, + { + // face 16 + new FaceOrientIJK(16, 0, 0, 0, 0), // central face + new FaceOrientIJK(17, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(15, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(11, 0, 2, 2, 3) // jk quadrant + }, + { + // face 17 + new FaceOrientIJK(17, 0, 0, 0, 0), // central face + new FaceOrientIJK(18, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(16, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(12, 0, 2, 2, 3) // jk quadrant + }, + { + // face 18 + new FaceOrientIJK(18, 0, 0, 0, 0), // central face + new FaceOrientIJK(19, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(17, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(13, 0, 2, 2, 3) // jk quadrant + }, + { + // face 19 + new FaceOrientIJK(19, 0, 0, 0, 0), // central face + new FaceOrientIJK(15, 2, 0, 2, 1), // ij quadrant + new FaceOrientIJK(18, 2, 2, 0, 5), // ki quadrant + new FaceOrientIJK(14, 0, 2, 2, 3) // jk quadrant + } }; + + int face; // face number + CoordIJK coord; // ijk coordinates on that face + + FaceIJK(int face, CoordIJK coord) { + this.face = face; + this.coord = coord; + } + + /** + * Adjusts this FaceIJK address so that the resulting cell address is + * relative to the correct icosahedral face. + * + * @param res The H3 resolution of the cell. + * @param pentLeading4 Whether or not the cell is a pentagon with a leading + * digit 4. + * @param substrate Whether or not the cell is in a substrate grid. + * @return 0 if on original face (no overage); 1 if on face edge (only occurs + * on substrate grids); 2 if overage on new face interior + */ + public Overage adjustOverageClassII(int res, boolean pentLeading4, boolean substrate) { + Overage overage = Overage.NO_OVERAGE; + // get the maximum dimension value; scale if a substrate grid + int maxDim = maxDimByCIIres[res]; + if (substrate) { + maxDim *= 3; + } + + // check for overage + if (substrate && this.coord.i + this.coord.j + this.coord.k == maxDim) { // on edge + overage = Overage.FACE_EDGE; + } else if (this.coord.i + this.coord.j + this.coord.k > maxDim) { // overage + overage = Overage.NEW_FACE; + final FaceOrientIJK fijkOrient; + if (this.coord.k > 0) { + if (this.coord.j > 0) { // jk "quadrant" + fijkOrient = faceNeighbors[this.face][JK]; + } else { // ik "quadrant" + fijkOrient = faceNeighbors[this.face][KI]; + // adjust for the pentagonal missing sequence + if (pentLeading4) { + // translate origin to center of pentagon + this.coord.ijkSub(maxDim, 0, 0); + // rotate to adjust for the missing sequence + this.coord.ijkRotate60cw(); + // translate the origin back to the center of the triangle + this.coord.ijkAdd(maxDim, 0, 0); + } + } + } else { // ij "quadrant" + fijkOrient = faceNeighbors[this.face][IJ]; + } + + this.face = fijkOrient.face; + + // rotate and translate for adjacent face + for (int i = 0; i < fijkOrient.ccwRot60; i++) { + this.coord.ijkRotate60ccw(); + } + + int unitScale = unitScaleByCIIres[res]; + if (substrate) { + unitScale *= 3; + } + this.coord.ijkAdd(fijkOrient.translateI * unitScale, fijkOrient.translateJ * unitScale, fijkOrient.translateK * unitScale); + this.coord.ijkNormalize(); + + // overage points on pentagon boundaries can end up on edges + if (substrate && this.coord.i + this.coord.j + this.coord.k == maxDim) { // on edge + overage = Overage.FACE_EDGE; + } + } + return overage; + } + + /** + * Computes the center point in spherical coordinates of a cell given by + * a FaceIJK address at a specified resolution. + * + * @param res The H3 resolution of the cell. + */ + public LatLng faceIjkToGeo(int res) { + Vec2d v = coord.ijkToHex2d(); + return v.hex2dToGeo(face, res, false); + } + + /** + * Computes the cell boundary in spherical coordinates for a pentagonal cell + * for this FaceIJK address at a specified resolution. + * + * @param res The H3 resolution of the cell. + * @param start The first topological vertex to return. + * @param length The number of topological vertexes to return. + */ + public CellBoundary faceIjkPentToCellBoundary(int res, int start, int length) { + FaceIJK[] fijkVerts = new FaceIJK[Constants.NUM_PENT_VERTS]; + int adjRes = faceIjkPentToVerts(res, fijkVerts); + + // If we're returning the entire loop, we need one more iteration in case + // of a distortion vertex on the last edge + int additionalIteration = length == Constants.NUM_PENT_VERTS ? 1 : 0; + + // convert each vertex to lat/lng + // adjust the face of each vertex as appropriate and introduce + // edge-crossing vertices as needed + CellBoundary boundary = new CellBoundary(); + FaceIJK lastFijk = null; + for (int vert = start; vert < start + length + additionalIteration; vert++) { + int v = vert % Constants.NUM_PENT_VERTS; + + FaceIJK fijk = fijkVerts[v]; + + fijk.adjustPentVertOverage(adjRes); + + // all Class III pentagon edges cross icosa edges + // note that Class II pentagons have vertices on the edge, + // not edge intersections + if (H3Index.isResolutionClassIII(res) && vert > start) { + // find hex2d of the two vertexes on the last face + FaceIJK tmpFijk = new FaceIJK(fijk.face, new CoordIJK(fijk.coord.i, fijk.coord.j, fijk.coord.k)); + + Vec2d orig2d0 = lastFijk.coord.ijkToHex2d(); + + int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; + + FaceOrientIJK fijkOrient = faceNeighbors[tmpFijk.face][currentToLastDir]; + + tmpFijk.face = fijkOrient.face; + CoordIJK ijk = tmpFijk.coord; + + // rotate and translate for adjacent face + for (int i = 0; i < fijkOrient.ccwRot60; i++) { + ijk.ijkRotate60ccw(); + } + + int unitScale = unitScaleByCIIres[adjRes] * 3; + ijk.ijkAdd(fijkOrient.translateI * unitScale, fijkOrient.translateJ * unitScale, fijkOrient.translateK * unitScale); + ijk.ijkNormalize(); + + Vec2d orig2d1 = ijk.ijkToHex2d(); + + // find the appropriate icosa face edge vertexes + int maxDim = maxDimByCIIres[adjRes]; + Vec2d v0 = new Vec2d(3.0 * maxDim, 0.0); + Vec2d v1 = new Vec2d(-1.5 * maxDim, 3.0 * Constants.M_SQRT3_2 * maxDim); + Vec2d v2 = new Vec2d(-1.5 * maxDim, -3.0 * Constants.M_SQRT3_2 * maxDim); + + Vec2d edge0; + Vec2d edge1; + switch (adjacentFaceDir[tmpFijk.face][fijk.face]) { + case IJ: + edge0 = v0; + edge1 = v1; + break; + case JK: + edge0 = v1; + edge1 = v2; + break; + case KI: + default: + assert (adjacentFaceDir[tmpFijk.face][fijk.face] == KI); + edge0 = v2; + edge1 = v0; + break; + } + + // find the intersection and add the lat/lng point to the result + Vec2d inter = Vec2d.v2dIntersect(orig2d0, orig2d1, edge0, edge1); + LatLng point = inter.hex2dToGeo(tmpFijk.face, adjRes, true); + boundary.add(point); + } + + // convert vertex to lat/lng and add to the result + // vert == start + NUM_PENT_VERTS is only used to test for possible + // intersection on last edge + if (vert < start + Constants.NUM_PENT_VERTS) { + Vec2d vec = fijk.coord.ijkToHex2d(); + LatLng point = vec.hex2dToGeo(fijk.face, adjRes, true); + boundary.add(point); + } + + lastFijk = fijk; + } + return boundary; + } + + /** + * Generates the cell boundary in spherical coordinates for a cell given by this + * FaceIJK address at a specified resolution. + * + * @param res The H3 resolution of the cell. + * @param start The first topological vertex to return. + * @param length The number of topological vertexes to return. + */ + public CellBoundary faceIjkToCellBoundary(int res, int start, int length) { + FaceIJK fijkVerts[] = new FaceIJK[Constants.NUM_HEX_VERTS]; + int adjRes = faceIjkToVerts(res, fijkVerts); + // If we're returning the entire loop, we need one more iteration in case + // of a distortion vertex on the last edge + int additionalIteration = length == Constants.NUM_HEX_VERTS ? 1 : 0; + + // convert each vertex to lat/lng + // adjust the face of each vertex as appropriate and introduce + // edge-crossing vertices as needed + CellBoundary boundary = new CellBoundary(); + int lastFace = -1; + Overage lastOverage = Overage.NO_OVERAGE; + for (int vert = start; vert < start + length + additionalIteration; vert++) { + int v = vert % Constants.NUM_HEX_VERTS; + + FaceIJK fijk = new FaceIJK(fijkVerts[v].face, new CoordIJK(fijkVerts[v].coord.i, fijkVerts[v].coord.j, fijkVerts[v].coord.k)); + + // + final boolean pentLeading4 = false; // may change in c code when calling method + Overage overage = fijk.adjustOverageClassII(adjRes, pentLeading4, true); + + /* + Check for edge-crossing. Each face of the underlying icosahedron is a + different projection plane. So if an edge of the hexagon crosses an + icosahedron edge, an additional vertex must be introduced at that + intersection point. Then each half of the cell edge can be projected + to geographic coordinates using the appropriate icosahedron face + projection. Note that Class II cell edges have vertices on the face + edge, with no edge line intersections. + */ + if (H3Index.isResolutionClassIII(res) && vert > start && fijk.face != lastFace && lastOverage != Overage.FACE_EDGE) { + // find hex2d of the two vertexes on original face + int lastV = (v + 5) % Constants.NUM_HEX_VERTS; + Vec2d orig2d0 = fijkVerts[lastV].coord.ijkToHex2d(); + Vec2d orig2d1 = fijkVerts[v].coord.ijkToHex2d(); + + // find the appropriate icosa face edge vertexes + int maxDim = maxDimByCIIres[adjRes]; + Vec2d v0 = new Vec2d(3.0 * maxDim, 0.0); + Vec2d v1 = new Vec2d(-1.5 * maxDim, 3.0 * Constants.M_SQRT3_2 * maxDim); + Vec2d v2 = new Vec2d(-1.5 * maxDim, -3.0 * Constants.M_SQRT3_2 * maxDim); + + int face2 = ((lastFace == this.face) ? fijk.face : lastFace); + final Vec2d edge0; + final Vec2d edge1; + switch (adjacentFaceDir[this.face][face2]) { + case IJ: + edge0 = v0; + edge1 = v1; + break; + case JK: + edge0 = v1; + edge1 = v2; + break; + // case KI: + default: + assert (adjacentFaceDir[this.face][face2] == KI); + edge0 = v2; + edge1 = v0; + break; + } + + // find the intersection and add the lat/lng point to the result + Vec2d inter = Vec2d.v2dIntersect(orig2d0, orig2d1, edge0, edge1); + /* + If a point of intersection occurs at a hexagon vertex, then each + adjacent hexagon edge will lie completely on a single icosahedron + face, and no additional vertex is required. + */ + boolean isIntersectionAtVertex = orig2d0.equals(inter) || orig2d1.equals(inter); + if (isIntersectionAtVertex == false) { + LatLng point = inter.hex2dToGeo(this.face, adjRes, true); + boundary.add(point); + } + } + + // convert vertex to lat/lng and add to the result + // vert == start + NUM_HEX_VERTS is only used to test for possible + // intersection on last edge + if (vert < start + Constants.NUM_HEX_VERTS) { + Vec2d vec = fijk.coord.ijkToHex2d(); + LatLng point = vec.hex2dToGeo(fijk.face, adjRes, true); + boundary.add(point); + } + lastFace = fijk.face; + lastOverage = overage; + } + return boundary; + } + + /** + * compute the corresponding H3Index. + * @param res The cell resolution. + * @return The encoded H3Index (or H3_NULL on failure). + */ + public long faceIjkToH3(int res) { + // initialize the index + long h = H3Index.H3_INIT; + h = H3Index.H3_set_mode(h, Constants.H3_CELL_MODE); + h = H3Index.H3_set_resolution(h, res); + + // check for res 0/base cell + if (res == 0) { + if (coord.i > MAX_FACE_COORD || coord.j > MAX_FACE_COORD || coord.k > MAX_FACE_COORD) { + // out of range input + throw new IllegalArgumentException(" out of range input"); + } + + return H3Index.H3_set_base_cell(h, BaseCells.getBaseCell(this)); + } + + // we need to find the correct base cell FaceIJK for this H3 index; + // start with the passed in face and resolution res ijk coordinates + // in that face's coordinate system + + // build the H3Index from finest res up + // adjust r for the fact that the res 0 base cell offsets the indexing + // digits + for (int r = res - 1; r >= 0; r--) { + int lastI = coord.i; + int lastJ = coord.j; + int lastK = coord.k; + CoordIJK lastCenter; + if (H3Index.isResolutionClassIII(r + 1)) { + // rotate ccw + coord.upAp7(); + lastCenter = new CoordIJK(coord.i, coord.j, coord.k); + lastCenter.downAp7(); + } else { + // rotate cw + coord.upAp7r(); + lastCenter = new CoordIJK(coord.i, coord.j, coord.k); + lastCenter.downAp7r(); + } + + CoordIJK diff = new CoordIJK(lastI - lastCenter.i, lastJ - lastCenter.j, lastK - lastCenter.k); + diff.ijkNormalize(); + h = H3Index.H3_set_index_digit(h, r + 1, diff.unitIjkToDigit()); + } + + // we should now hold the IJK of the base cell in the + // coordinate system of the current face + + if (coord.i > MAX_FACE_COORD || coord.j > MAX_FACE_COORD || coord.k > MAX_FACE_COORD) { + // out of range input + throw new IllegalArgumentException(" out of range input"); + } + + // lookup the correct base cell + int baseCell = BaseCells.getBaseCell(this); + h = H3Index.H3_set_base_cell(h, baseCell); + + // rotate if necessary to get canonical base cell orientation + // for this base cell + int numRots = BaseCells.getBaseCellCCWrot60(this); + if (BaseCells.isBaseCellPentagon(baseCell)) { + // force rotation out of missing k-axes sub-sequence + if (H3Index.h3LeadingNonZeroDigit(h) == CoordIJK.Direction.K_AXES_DIGIT.digit()) { + // check for a cw/ccw offset face; default is ccw + if (BaseCells.baseCellIsCwOffset(baseCell, face)) { + h = H3Index.h3Rotate60cw(h); + } else { + h = H3Index.h3Rotate60ccw(h); + } + } + + for (int i = 0; i < numRots; i++) { + h = H3Index.h3RotatePent60ccw(h); + } + } else { + for (int i = 0; i < numRots; i++) { + h = H3Index.h3Rotate60ccw(h); + } + } + + return h; + } + + /** + * Populate the vertices of this cell as substrate FaceIJK addresses. + * + * @param res The H3 resolution of the cell. This may be adjusted if + * necessary for the substrate grid resolution. + */ + private int faceIjkToVerts(int res, FaceIJK[] fijkVerts) { + // get the correct set of substrate vertices for this resolution + CoordIJK[] verts; + if (H3Index.isResolutionClassIII(res)) { + // the vertexes of an origin-centered cell in a Class III resolution on a + // substrate grid with aperture sequence 33r7r. The aperture 3 gets us the + // vertices, and the 3r7r gets us to Class II. + // vertices listed ccw from the i-axes + verts = new CoordIJK[] { + new CoordIJK(5, 4, 0), // 0 + new CoordIJK(1, 5, 0), // 1 + new CoordIJK(0, 5, 4), // 2 + new CoordIJK(0, 1, 5), // 3 + new CoordIJK(4, 0, 5), // 4 + new CoordIJK(5, 0, 1) // 5 + }; + } else { + // the vertexes of an origin-centered cell in a Class II resolution on a + // substrate grid with aperture sequence 33r. The aperture 3 gets us the + // vertices, and the 3r gets us back to Class II. + // vertices listed ccw from the i-axes + verts = new CoordIJK[] { + new CoordIJK(2, 1, 0), // 0 + new CoordIJK(1, 2, 0), // 1 + new CoordIJK(0, 2, 1), // 2 + new CoordIJK(0, 1, 2), // 3 + new CoordIJK(1, 0, 2), // 4 + new CoordIJK(2, 0, 1) // 5 + }; + } + + // adjust the center point to be in an aperture 33r substrate grid + // these should be composed for speed + this.coord.downAp3(); + this.coord.downAp3r(); + + // if res is Class III we need to add a cw aperture 7 to get to + // icosahedral Class II + if (H3Index.isResolutionClassIII(res)) { + this.coord.downAp7r(); + res += 1; + } + + // The center point is now in the same substrate grid as the origin + // cell vertices. Add the center point substate coordinates + // to each vertex to translate the vertices to that cell. + + for (int v = 0; v < Constants.NUM_HEX_VERTS; v++) { + verts[v].ijkAdd(this.coord.i, this.coord.j, this.coord.k); + verts[v].ijkNormalize(); + fijkVerts[v] = new FaceIJK(this.face, verts[v]); + } + return res; + } + + /** + * Populate the vertices of this pentagon cell as substrate FaceIJK addresses + * + * @param res The H3 resolution of the cell. This may be adjusted if + * necessary for the substrate grid resolution. + */ + private int faceIjkPentToVerts(int res, FaceIJK[] fijkVerts) { + // get the correct set of substrate vertices for this resolution + CoordIJK[] verts; + if (H3Index.isResolutionClassIII(res)) { + // the vertexes of an origin-centered pentagon in a Class II resolution on a + // substrate grid with aperture sequence 33r. The aperture 3 gets us the + // vertices, and the 3r gets us back to Class II. + // vertices listed ccw from the i-axes + verts = new CoordIJK[] { + new CoordIJK(5, 4, 0), // 0 + new CoordIJK(1, 5, 0), // 1 + new CoordIJK(0, 5, 4), // 2 + new CoordIJK(0, 1, 5), // 3 + new CoordIJK(4, 0, 5) // 4 + }; + } else { + // the vertexes of an origin-centered pentagon in a Class III resolution on + // a substrate grid with aperture sequence 33r7r. The aperture 3 gets us the + // vertices, and the 3r7r gets us to Class II. vertices listed ccw from the + // i-axes + verts = new CoordIJK[] { + new CoordIJK(2, 1, 0), // 0 + new CoordIJK(1, 2, 0), // 1 + new CoordIJK(0, 2, 1), // 2 + new CoordIJK(0, 1, 2), // 3 + new CoordIJK(1, 0, 2) // 4 + }; + } + + // adjust the center point to be in an aperture 33r substrate grid + // these should be composed for speed + this.coord.downAp3(); + this.coord.downAp3r(); + + // if res is Class III we need to add a cw aperture 7 to get to + // icosahedral Class II + if (H3Index.isResolutionClassIII(res)) { + this.coord.downAp7r(); + res += 1; + } + + // The center point is now in the same substrate grid as the origin + // cell vertices. Add the center point substate coordinates + // to each vertex to translate the vertices to that cell. + for (int v = 0; v < Constants.NUM_PENT_VERTS; v++) { + verts[v].ijkAdd(this.coord.i, this.coord.j, this.coord.k); + verts[v].ijkNormalize(); + fijkVerts[v] = new FaceIJK(this.face, verts[v]); + } + return res; + } + + /** + * Adjusts a FaceIJK address for a pentagon vertex in a substrate grid in + * place so that the resulting cell address is relative to the correct + * icosahedral face. + * + * @param res The H3 resolution of the cell. + */ + private Overage adjustPentVertOverage(int res) { + Overage overage; + do { + overage = adjustOverageClassII(res, false, true); + } while (overage == Overage.NEW_FACE); + return overage; + } +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/H3.java b/libs/h3/src/main/java/org/elasticsearch/h3/H3.java new file mode 100644 index 0000000000000..82f01a056fc14 --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/H3.java @@ -0,0 +1,306 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2021 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +import java.util.Arrays; + +import static java.lang.Math.toRadians; + +/** + * Defines the public API of the H3 library. + */ +public final class H3 { + + public static int MAX_H3_RES = Constants.MAX_H3_RES; + + /** + * Converts from long representation of an index to String representation. + */ + public static String h3ToString(long h3) { + return Long.toHexString(h3); + } + + /** + * Converts from String representation of an index to long representation. + */ + public static long stringToH3(String h3Address) { + return Long.parseUnsignedLong(h3Address, 16); + } + + /** determines if an H3 cell is a pentagon */ + public static boolean isPentagon(long h3) { + return H3Index.H3_is_pentagon(h3); + } + + /** determines if an H3 cell in string format is a pentagon */ + public static boolean isPentagon(String h3Address) { + return isPentagon(stringToH3(h3Address)); + } + + /** Returns true if this is a valid H3 index */ + public static boolean h3IsValid(long h3) { + if (H3Index.H3_get_high_bit(h3) != 0) { + return false; + } + + if (H3Index.H3_get_mode(h3) != Constants.H3_CELL_MODE) { + return false; + } + + if (H3Index.H3_get_reserved_bits(h3) != 0) { + return false; + } + + int baseCell = H3Index.H3_get_base_cell(h3); + if (baseCell < 0 || baseCell >= Constants.NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + return false; + } + + int res = H3Index.H3_get_resolution(h3); + if (res < 0 || res > Constants.MAX_H3_RES) { // LCOV_EXCL_BR_LINE + // Resolutions less than zero can not be represented in an index + return false; + } + + boolean foundFirstNonZeroDigit = false; + for (int r = 1; r <= res; r++) { + int digit = H3Index.H3_get_index_digit(h3, r); + + if (foundFirstNonZeroDigit == false && digit != CoordIJK.Direction.CENTER_DIGIT.digit()) { + foundFirstNonZeroDigit = true; + if (BaseCells.isBaseCellPentagon(baseCell) && digit == CoordIJK.Direction.K_AXES_DIGIT.digit()) { + return false; + } + } + + if (digit < CoordIJK.Direction.CENTER_DIGIT.digit() || digit >= CoordIJK.Direction.NUM_DIGITS.digit()) { + return false; + } + } + + for (int r = res + 1; r <= Constants.MAX_H3_RES; r++) { + int digit = H3Index.H3_get_index_digit(h3, r); + if (digit != CoordIJK.Direction.INVALID_DIGIT.digit()) { + return false; + } + } + return true; + } + + /** Returns true if this is a valid H3 index */ + public static boolean h3IsValid(String h3Address) { + return h3IsValid(stringToH3(h3Address)); + } + + /** + * Return all base cells + */ + public static long[] getLongRes0Cells() { + long[] cells = new long[Constants.NUM_BASE_CELLS]; + for (int bc = 0; bc < Constants.NUM_BASE_CELLS; bc++) { + long baseCell = H3Index.H3_INIT; + baseCell = H3Index.H3_set_mode(baseCell, Constants.H3_CELL_MODE); + baseCell = H3Index.H3_set_base_cell(baseCell, bc); + cells[bc] = baseCell; + } + return cells; + } + + /** + * Return all base cells + */ + public static String[] getStringRes0Cells() { + return h3ToStringList(getLongRes0Cells()); + } + + /** + * Find the {@link LatLng} center point of the cell. + */ + public static LatLng h3ToLatLng(long h3) { + final FaceIJK fijk = H3Index.h3ToFaceIjk(h3); + return fijk.faceIjkToGeo(H3Index.H3_get_resolution(h3)); + } + + /** + * Find the {@link LatLng} center point of the cell. + */ + public static LatLng h3ToLatLng(String h3Address) { + return h3ToLatLng(stringToH3(h3Address)); + } + + /** + * Find the cell {@link CellBoundary} coordinates for the cell + */ + public static CellBoundary h3ToGeoBoundary(long h3) { + FaceIJK fijk = H3Index.h3ToFaceIjk(h3); + if (H3Index.H3_is_pentagon(h3)) { + return fijk.faceIjkPentToCellBoundary(H3Index.H3_get_resolution(h3), 0, Constants.NUM_PENT_VERTS); + } else { + return fijk.faceIjkToCellBoundary(H3Index.H3_get_resolution(h3), 0, Constants.NUM_HEX_VERTS); + } + } + + /** + * Find the cell {@link CellBoundary} coordinates for the cell + */ + public static CellBoundary h3ToGeoBoundary(String h3Address) { + return h3ToGeoBoundary(stringToH3(h3Address)); + } + + /** + * Find the H3 index of the resolution res cell containing the lat/lon (in degrees) + * + * @param lat Latitude in degrees. + * @param lng Longitude in degrees. + * @param res Resolution, 0 <= res <= 15 + * @return The H3 index. + * @throws IllegalArgumentException latitude, longitude, or resolution are out of range. + */ + public static long geoToH3(double lat, double lng, int res) { + checkResolution(res); + return new LatLng(toRadians(lat), toRadians(lng)).geoToFaceIJK(res).faceIjkToH3(res); + } + + /** + * Find the H3 index of the resolution res cell containing the lat/lon (in degrees) + * + * @param lat Latitude in degrees. + * @param lng Longitude in degrees. + * @param res Resolution, 0 <= res <= 15 + * @return The H3 index. + * @throws IllegalArgumentException Latitude, longitude, or resolution is out of range. + */ + public static String geoToH3Address(double lat, double lng, int res) { + return h3ToString(geoToH3(lat, lng, res)); + } + + /** + * Returns the parent of the given index. + */ + public static long h3ToParent(long h3) { + int childRes = H3Index.H3_get_resolution(h3); + if (childRes == 0) { + throw new IllegalArgumentException("Input is a base cell"); + } + long parentH = H3Index.H3_set_resolution(h3, childRes - 1); + return H3Index.H3_set_index_digit(parentH, childRes, H3Index.H3_DIGIT_MASK); + } + + /** + * Returns the parent of the given index. + */ + public static String h3ToParent(String h3Address) { + long parent = h3ToParent(stringToH3(h3Address)); + return h3ToString(parent); + } + + /** + * Returns the children of the given index. + */ + public static long[] h3ToChildren(long h3) { + long[] children = new long[cellToChildrenSize(h3)]; + int res = H3Index.H3_get_resolution(h3); + Iterator.IterCellsChildren it = Iterator.iterInitParent(h3, res + 1); + int pos = 0; + while (it.h != Iterator.H3_NULL) { + children[pos++] = it.h; + Iterator.iterStepChild(it); + } + return children; + } + + /** + * Transforms a list of H3 indexes in long form to a list of H3 + * indexes in string form. + */ + public static String[] h3ToChildren(String h3Address) { + return h3ToStringList(h3ToChildren(stringToH3(h3Address))); + } + + public static String[] hexRing(String h3Address) { + return h3ToStringList(hexRing(stringToH3(h3Address))); + } + + /** + * Returns the neighbor indexes. + * + * @param h3 Origin index + * @return All neighbor indexes from the origin + */ + public static long[] hexRing(long h3) { + return HexRing.hexRing(h3); + } + + /** + * cellToChildrenSize returns the exact number of children for a cell at a + * given child resolution. + * + * @param h H3Index to find the number of children of + * + * @return int Exact number of children (handles hexagons and pentagons + * correctly) + */ + private static int cellToChildrenSize(long h) { + int n = 1; + if (H3Index.H3_is_pentagon(h)) { + return (1 + 5 * (_ipow(7, n) - 1) / 6); + } else { + return _ipow(7, n); + } + } + + /** + * _ipow does integer exponentiation efficiently. Taken from StackOverflow. + * + * @param base the integer base (can be positive or negative) + * @param exp the integer exponent (should be nonnegative) + * + * @return the exponentiated value + */ + private static int _ipow(int base, int exp) { + int result = 1; + while (exp != 0) { + if ((exp & 1) != 0) { + result *= base; + } + exp >>= 1; + base *= base; + } + + return result; + } + + private static String[] h3ToStringList(long[] h3s) { + return Arrays.stream(h3s).mapToObj(H3::h3ToString).toArray(String[]::new); + } + + /** + * @throws IllegalArgumentException res is not a valid H3 resolution. + */ + private static void checkResolution(int res) { + if (res < 0 || res > Constants.MAX_H3_RES) { + throw new IllegalArgumentException("resolution [" + res + "] is out of range (must be 0 <= res <= 15)"); + } + } +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/H3Index.java b/libs/h3/src/main/java/org/elasticsearch/h3/H3Index.java new file mode 100644 index 0000000000000..bc805157bc9c8 --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/H3Index.java @@ -0,0 +1,333 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2018, 2020 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** + * Functions that can be applied to an H3 index. + */ +final class H3Index { + + /** + * Gets the integer base cell of h3. + */ + public static int H3_get_base_cell(long h3) { + return ((int) ((((h3) & H3_BC_MASK) >> H3_BC_OFFSET))); + } + + /** + * Returns true if this index is one of twelve pentagons per resolution. + */ + public static boolean H3_is_pentagon(long h3) { + return BaseCells.isBaseCellPentagon(H3Index.H3_get_base_cell(h3)) && H3Index.h3LeadingNonZeroDigit(h3) == 0; + } + + public static long H3_INIT = 35184372088831L; + + /** + * The bit offset of the mode in an H3 index. + */ + public static int H3_MODE_OFFSET = 59; + + /** + * 1's in the 4 mode bits, 0's everywhere else. + */ + public static long H3_MODE_MASK = 15L << H3_MODE_OFFSET; + + /** + * 0's in the 4 mode bits, 1's everywhere else. + */ + public static long H3_MODE_MASK_NEGATIVE = ~H3_MODE_MASK; + + public static long H3_set_mode(long h3, long mode) { + return (h3 & H3_MODE_MASK_NEGATIVE) | (mode << H3_MODE_OFFSET); + } + + /** + * The bit offset of the base cell in an H3 index. + */ + public static int H3_BC_OFFSET = 45; + /** + * 1's in the 7 base cell bits, 0's everywhere else. + */ + public static long H3_BC_MASK = 127L << H3_BC_OFFSET; + + /** + * 0's in the 7 base cell bits, 1's everywhere else. + */ + public static long H3_BC_MASK_NEGATIVE = ~H3_BC_MASK; + + /** + * Sets the integer base cell of h3 to bc. + */ + public static long H3_set_base_cell(long h3, long bc) { + return (h3 & H3_BC_MASK_NEGATIVE) | (bc << H3_BC_OFFSET); + } + + public static int H3_RES_OFFSET = 52; + /** + * 1's in the 4 resolution bits, 0's everywhere else. + */ + public static long H3_RES_MASK = 15L << H3_RES_OFFSET; + + /** + * 0's in the 4 resolution bits, 1's everywhere else. + */ + public static long H3_RES_MASK_NEGATIVE = ~H3_RES_MASK; + + /** + * The bit offset of the max resolution digit in an H3 index. + */ + public static int H3_MAX_OFFSET = 63; + + /** + * 1 in the highest bit, 0's everywhere else. + */ + public static long H3_HIGH_BIT_MASK = (1L << H3_MAX_OFFSET); + + /** + * Gets the highest bit of the H3 index. + */ + public static int H3_get_high_bit(long h3) { + return ((int) ((((h3) & H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))); + } + + /** + * Sets the long resolution of h3. + */ + public static long H3_set_resolution(long h3, long res) { + return (((h3) & H3_RES_MASK_NEGATIVE) | (((res)) << H3_RES_OFFSET)); + } + + /** + * The bit offset of the reserved bits in an H3 index. + */ + public static int H3_RESERVED_OFFSET = 56; + + /** + * 1's in the 3 reserved bits, 0's everywhere else. + */ + public static long H3_RESERVED_MASK = (7L << H3_RESERVED_OFFSET); + + /** + * Gets a value in the reserved space. Should always be zero for valid indexes. + */ + public static int H3_get_reserved_bits(long h3) { + return ((int) ((((h3) & H3_RESERVED_MASK) >> H3_RESERVED_OFFSET))); + } + + public static int H3_get_mode(long h3) { + return ((int) ((((h3) & H3_MODE_MASK) >> H3_MODE_OFFSET))); + } + + /** + * Gets the integer resolution of h3. + */ + public static int H3_get_resolution(long h3) { + return (int) ((h3 & H3_RES_MASK) >> H3_RES_OFFSET); + } + + /** + * The number of bits in a single H3 resolution digit. + */ + public static int H3_PER_DIGIT_OFFSET = 3; + + /** + * 1's in the 3 bits of res 15 digit bits, 0's everywhere else. + */ + public static long H3_DIGIT_MASK = 7L; + + /** + * Gets the resolution res integer digit (0-7) of h3. + */ + public static int H3_get_index_digit(long h3, int res) { + return ((int) ((((h3) >> ((Constants.MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET)) & H3_DIGIT_MASK))); + } + + /** + * Sets the resolution res digit of h3 to the integer digit (0-7) + */ + public static long H3_set_index_digit(long h3, int res, long digit) { + int x = (Constants.MAX_H3_RES - res) * H3_PER_DIGIT_OFFSET; + return (((h3) & ~((H3_DIGIT_MASK << (x)))) | (((digit)) << x)); + } + + /** + * Returns whether or not a resolution is a Class III grid. Note that odd + * resolutions are Class III and even resolutions are Class II. + * @param res The H3 resolution. + * @return 1 if the resolution is a Class III grid, and 0 if the resolution is + * a Class II grid. + */ + public static boolean isResolutionClassIII(int res) { + return res % 2 != 0; + } + + /** + * Convert an H3Index to a FaceIJK address. + * @param h3 The H3Index. + */ + public static FaceIJK h3ToFaceIjk(long h3) { + int baseCell = H3Index.H3_get_base_cell(h3); + if (baseCell < 0 || baseCell >= Constants.NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + // To prevent reading uninitialized memory, we zero the output. + throw new IllegalArgumentException(); + } + // adjust for the pentagonal missing sequence; all of sub-sequence 5 needs + // to be adjusted (and some of sub-sequence 4 below) + if (BaseCells.isBaseCellPentagon(baseCell) && h3LeadingNonZeroDigit(h3) == 5) { + h3 = h3Rotate60cw(h3); + } + + // start with the "home" face and ijk+ coordinates for the base cell of c + FaceIJK fijk = BaseCells.getBaseFaceIJK(baseCell); + if (h3ToFaceIjkWithInitializedFijk(h3, fijk) == false) { + return fijk; // no overage is possible; h lies on this face + } + // if we're here we have the potential for an "overage"; i.e., it is + // possible that c lies on an adjacent face + + CoordIJK origIJK = new CoordIJK(fijk.coord.i, fijk.coord.j, fijk.coord.k); + + // if we're in Class III, drop into the next finer Class II grid + int res = H3Index.H3_get_resolution(h3); + if (isResolutionClassIII(res)) { + // Class III + fijk.coord.downAp7r(); + res++; + } + + // adjust for overage if needed + // a pentagon base cell with a leading 4 digit requires special handling + boolean pentLeading4 = (BaseCells.isBaseCellPentagon(baseCell) && h3LeadingNonZeroDigit(h3) == 4); + if (fijk.adjustOverageClassII(res, pentLeading4, false) != FaceIJK.Overage.NO_OVERAGE) { + // if the base cell is a pentagon we have the potential for secondary + // overages + if (BaseCells.isBaseCellPentagon(baseCell)) { + FaceIJK.Overage overage; + do { + overage = fijk.adjustOverageClassII(res, false, false); + } while (overage != FaceIJK.Overage.NO_OVERAGE); + } + + if (res != H3Index.H3_get_resolution(h3)) { + fijk.coord.upAp7r(); + } + } else if (res != H3Index.H3_get_resolution(h3)) { + fijk.coord = origIJK; + } + return fijk; + } + + /** + * Returns the highest resolution non-zero digit in an H3Index. + * @param h The H3Index. + * @return The highest resolution non-zero digit in the H3Index. + */ + public static int h3LeadingNonZeroDigit(long h) { + for (int r = 1; r <= H3Index.H3_get_resolution(h); r++) { + final int dir = H3Index.H3_get_index_digit(h, r); + if (dir != CoordIJK.Direction.CENTER_DIGIT.digit()) { + return dir; + } + } + // if we're here it's all 0's + return CoordIJK.Direction.CENTER_DIGIT.digit(); + } + + /** + * Convert an H3Index to the FaceIJK address on a specified icosahedral face. + * @param h The H3Index. + * @param fijk The FaceIJK address, initialized with the desired face + * and normalized base cell coordinates. + * @return Returns true if the possibility of overage exists, otherwise false. + */ + private static boolean h3ToFaceIjkWithInitializedFijk(long h, FaceIJK fijk) { + + final int res = H3Index.H3_get_resolution(h); + + // center base cell hierarchy is entirely on this face + final boolean possibleOverage = BaseCells.isBaseCellPentagon(H3_get_base_cell(h)) != false + || (res != 0 && (fijk.coord.i != 0 || fijk.coord.j != 0 || fijk.coord.k != 0)); + + for (int r = 1; r <= res; r++) { + if (isResolutionClassIII(r)) { + // Class III == rotate ccw + fijk.coord.downAp7(); + } else { + // Class II == rotate cw + fijk.coord.downAp7r(); + } + fijk.coord.neighbor(H3_get_index_digit(h, r)); + } + + return possibleOverage; + } + + /** + * Rotate an H3Index 60 degrees clockwise. + * @param h The H3Index. + */ + public static long h3Rotate60cw(long h) { + for (int r = 1, res = H3_get_resolution(h); r <= res; r++) { + h = H3_set_index_digit(h, r, CoordIJK.rotate60cw(H3_get_index_digit(h, r))); + } + return h; + } + + /** + * Rotate an H3Index 60 degrees counter-clockwise. + * @param h The H3Index. + */ + public static long h3Rotate60ccw(long h) { + for (int r = 1, res = H3_get_resolution(h); r <= res; r++) { + h = H3_set_index_digit(h, r, CoordIJK.rotate60ccw(H3_get_index_digit(h, r))); + } + return h; + } + + /** + * Rotate an H3Index 60 degrees counter-clockwise about a pentagonal center. + * @param h The H3Index. + */ + public static long h3RotatePent60ccw(long h) { + // skips any leading 1 digits (k-axis) + boolean foundFirstNonZeroDigit = false; + for (int r = 1, res = H3_get_resolution(h); r <= res; r++) { + // rotate this digit + h = H3_set_index_digit(h, r, CoordIJK.rotate60ccw(H3_get_index_digit(h, r))); + + // look for the first non-zero digit so we + // can adjust for deleted k-axes sequence + // if necessary + if (foundFirstNonZeroDigit == false && H3_get_index_digit(h, r) != 0) { + foundFirstNonZeroDigit = true; + + // adjust for deleted k-axes sequence + if (h3LeadingNonZeroDigit(h) == CoordIJK.Direction.K_AXES_DIGIT.digit()) h = h3Rotate60ccw(h); + } + } + return h; + } + +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/HexRing.java b/libs/h3/src/main/java/org/elasticsearch/h3/HexRing.java new file mode 100644 index 0000000000000..567d96fe07007 --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/HexRing.java @@ -0,0 +1,756 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2021 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** + * Computes the neighbour H3 index from a given index. + */ +final class HexRing { + + private static final int INVALID_BASE_CELL = 127; + + /** Neighboring base cell ID in each IJK direction. + * + * For each base cell, for each direction, the neighboring base + * cell ID is given. 127 indicates there is no neighbor in that direction. + */ + private static final int[][] baseCellNeighbors = new int[][] { + { 0, 1, 5, 2, 4, 3, 8 }, // base cell 0 + { 1, 7, 6, 9, 0, 3, 2 }, // base cell 1 + { 2, 6, 10, 11, 0, 1, 5 }, // base cell 2 + { 3, 13, 1, 7, 4, 12, 0 }, // base cell 3 + { 4, INVALID_BASE_CELL, 15, 8, 3, 0, 12 }, // base cell 4 (pentagon) + { 5, 2, 18, 10, 8, 0, 16 }, // base cell 5 + { 6, 14, 11, 17, 1, 9, 2 }, // base cell 6 + { 7, 21, 9, 19, 3, 13, 1 }, // base cell 7 + { 8, 5, 22, 16, 4, 0, 15 }, // base cell 8 + { 9, 19, 14, 20, 1, 7, 6 }, // base cell 9 + { 10, 11, 24, 23, 5, 2, 18 }, // base cell 10 + { 11, 17, 23, 25, 2, 6, 10 }, // base cell 11 + { 12, 28, 13, 26, 4, 15, 3 }, // base cell 12 + { 13, 26, 21, 29, 3, 12, 7 }, // base cell 13 + { 14, INVALID_BASE_CELL, 17, 27, 9, 20, 6 }, // base cell 14 (pentagon) + { 15, 22, 28, 31, 4, 8, 12 }, // base cell 15 + { 16, 18, 33, 30, 8, 5, 22 }, // base cell 16 + { 17, 11, 14, 6, 35, 25, 27 }, // base cell 17 + { 18, 24, 30, 32, 5, 10, 16 }, // base cell 18 + { 19, 34, 20, 36, 7, 21, 9 }, // base cell 19 + { 20, 14, 19, 9, 40, 27, 36 }, // base cell 20 + { 21, 38, 19, 34, 13, 29, 7 }, // base cell 21 + { 22, 16, 41, 33, 15, 8, 31 }, // base cell 22 + { 23, 24, 11, 10, 39, 37, 25 }, // base cell 23 + { 24, INVALID_BASE_CELL, 32, 37, 10, 23, 18 }, // base cell 24 (pentagon) + { 25, 23, 17, 11, 45, 39, 35 }, // base cell 25 + { 26, 42, 29, 43, 12, 28, 13 }, // base cell 26 + { 27, 40, 35, 46, 14, 20, 17 }, // base cell 27 + { 28, 31, 42, 44, 12, 15, 26 }, // base cell 28 + { 29, 43, 38, 47, 13, 26, 21 }, // base cell 29 + { 30, 32, 48, 50, 16, 18, 33 }, // base cell 30 + { 31, 41, 44, 53, 15, 22, 28 }, // base cell 31 + { 32, 30, 24, 18, 52, 50, 37 }, // base cell 32 + { 33, 30, 49, 48, 22, 16, 41 }, // base cell 33 + { 34, 19, 38, 21, 54, 36, 51 }, // base cell 34 + { 35, 46, 45, 56, 17, 27, 25 }, // base cell 35 + { 36, 20, 34, 19, 55, 40, 54 }, // base cell 36 + { 37, 39, 52, 57, 24, 23, 32 }, // base cell 37 + { 38, INVALID_BASE_CELL, 34, 51, 29, 47, 21 }, // base cell 38 (pentagon) + { 39, 37, 25, 23, 59, 57, 45 }, // base cell 39 + { 40, 27, 36, 20, 60, 46, 55 }, // base cell 40 + { 41, 49, 53, 61, 22, 33, 31 }, // base cell 41 + { 42, 58, 43, 62, 28, 44, 26 }, // base cell 42 + { 43, 62, 47, 64, 26, 42, 29 }, // base cell 43 + { 44, 53, 58, 65, 28, 31, 42 }, // base cell 44 + { 45, 39, 35, 25, 63, 59, 56 }, // base cell 45 + { 46, 60, 56, 68, 27, 40, 35 }, // base cell 46 + { 47, 38, 43, 29, 69, 51, 64 }, // base cell 47 + { 48, 49, 30, 33, 67, 66, 50 }, // base cell 48 + { 49, INVALID_BASE_CELL, 61, 66, 33, 48, 41 }, // base cell 49 (pentagon) + { 50, 48, 32, 30, 70, 67, 52 }, // base cell 50 + { 51, 69, 54, 71, 38, 47, 34 }, // base cell 51 + { 52, 57, 70, 74, 32, 37, 50 }, // base cell 52 + { 53, 61, 65, 75, 31, 41, 44 }, // base cell 53 + { 54, 71, 55, 73, 34, 51, 36 }, // base cell 54 + { 55, 40, 54, 36, 72, 60, 73 }, // base cell 55 + { 56, 68, 63, 77, 35, 46, 45 }, // base cell 56 + { 57, 59, 74, 78, 37, 39, 52 }, // base cell 57 + { 58, INVALID_BASE_CELL, 62, 76, 44, 65, 42 }, // base cell 58 (pentagon) + { 59, 63, 78, 79, 39, 45, 57 }, // base cell 59 + { 60, 72, 68, 80, 40, 55, 46 }, // base cell 60 + { 61, 53, 49, 41, 81, 75, 66 }, // base cell 61 + { 62, 43, 58, 42, 82, 64, 76 }, // base cell 62 + { 63, INVALID_BASE_CELL, 56, 45, 79, 59, 77 }, // base cell 63 (pentagon) + { 64, 47, 62, 43, 84, 69, 82 }, // base cell 64 + { 65, 58, 53, 44, 86, 76, 75 }, // base cell 65 + { 66, 67, 81, 85, 49, 48, 61 }, // base cell 66 + { 67, 66, 50, 48, 87, 85, 70 }, // base cell 67 + { 68, 56, 60, 46, 90, 77, 80 }, // base cell 68 + { 69, 51, 64, 47, 89, 71, 84 }, // base cell 69 + { 70, 67, 52, 50, 83, 87, 74 }, // base cell 70 + { 71, 89, 73, 91, 51, 69, 54 }, // base cell 71 + { 72, INVALID_BASE_CELL, 73, 55, 80, 60, 88 }, // base cell 72 (pentagon) + { 73, 91, 72, 88, 54, 71, 55 }, // base cell 73 + { 74, 78, 83, 92, 52, 57, 70 }, // base cell 74 + { 75, 65, 61, 53, 94, 86, 81 }, // base cell 75 + { 76, 86, 82, 96, 58, 65, 62 }, // base cell 76 + { 77, 63, 68, 56, 93, 79, 90 }, // base cell 77 + { 78, 74, 59, 57, 95, 92, 79 }, // base cell 78 + { 79, 78, 63, 59, 93, 95, 77 }, // base cell 79 + { 80, 68, 72, 60, 99, 90, 88 }, // base cell 80 + { 81, 85, 94, 101, 61, 66, 75 }, // base cell 81 + { 82, 96, 84, 98, 62, 76, 64 }, // base cell 82 + { 83, INVALID_BASE_CELL, 74, 70, 100, 87, 92 }, // base cell 83 (pentagon) + { 84, 69, 82, 64, 97, 89, 98 }, // base cell 84 + { 85, 87, 101, 102, 66, 67, 81 }, // base cell 85 + { 86, 76, 75, 65, 104, 96, 94 }, // base cell 86 + { 87, 83, 102, 100, 67, 70, 85 }, // base cell 87 + { 88, 72, 91, 73, 99, 80, 105 }, // base cell 88 + { 89, 97, 91, 103, 69, 84, 71 }, // base cell 89 + { 90, 77, 80, 68, 106, 93, 99 }, // base cell 90 + { 91, 73, 89, 71, 105, 88, 103 }, // base cell 91 + { 92, 83, 78, 74, 108, 100, 95 }, // base cell 92 + { 93, 79, 90, 77, 109, 95, 106 }, // base cell 93 + { 94, 86, 81, 75, 107, 104, 101 }, // base cell 94 + { 95, 92, 79, 78, 109, 108, 93 }, // base cell 95 + { 96, 104, 98, 110, 76, 86, 82 }, // base cell 96 + { 97, INVALID_BASE_CELL, 98, 84, 103, 89, 111 }, // base cell 97 (pentagon) + { 98, 110, 97, 111, 82, 96, 84 }, // base cell 98 + { 99, 80, 105, 88, 106, 90, 113 }, // base cell 99 + { 100, 102, 83, 87, 108, 114, 92 }, // base cell 100 + { 101, 102, 107, 112, 81, 85, 94 }, // base cell 101 + { 102, 101, 87, 85, 114, 112, 100 }, // base cell 102 + { 103, 91, 97, 89, 116, 105, 111 }, // base cell 103 + { 104, 107, 110, 115, 86, 94, 96 }, // base cell 104 + { 105, 88, 103, 91, 113, 99, 116 }, // base cell 105 + { 106, 93, 99, 90, 117, 109, 113 }, // base cell 106 + { 107, INVALID_BASE_CELL, 101, 94, 115, 104, 112 }, // base cell 107 (pentagon) + { 108, 100, 95, 92, 118, 114, 109 }, // base cell 108 + { 109, 108, 93, 95, 117, 118, 106 }, // base cell 109 + { 110, 98, 104, 96, 119, 111, 115 }, // base cell 110 + { 111, 97, 110, 98, 116, 103, 119 }, // base cell 111 + { 112, 107, 102, 101, 120, 115, 114 }, // base cell 112 + { 113, 99, 116, 105, 117, 106, 121 }, // base cell 113 + { 114, 112, 100, 102, 118, 120, 108 }, // base cell 114 + { 115, 110, 107, 104, 120, 119, 112 }, // base cell 115 + { 116, 103, 119, 111, 113, 105, 121 }, // base cell 116 + { 117, INVALID_BASE_CELL, 109, 118, 113, 121, 106 }, // base cell 117 (pentagon) + { 118, 120, 108, 114, 117, 121, 109 }, // base cell 118 + { 119, 111, 115, 110, 121, 116, 120 }, // base cell 119 + { 120, 115, 114, 112, 121, 119, 118 }, // base cell 120 + { 121, 116, 120, 119, 117, 113, 118 }, // base cell 121 + }; + + /** @brief Neighboring base cell rotations in each IJK direction. + * + * For each base cell, for each direction, the number of 60 degree + * CCW rotations to the coordinate system of the neighbor is given. + * -1 indicates there is no neighbor in that direction. + */ + private static final int[][] baseCellNeighbor60CCWRots = new int[][] { + { 0, 5, 0, 0, 1, 5, 1 }, // base cell 0 + { 0, 0, 1, 0, 1, 0, 1 }, // base cell 1 + { 0, 0, 0, 0, 0, 5, 0 }, // base cell 2 + { 0, 5, 0, 0, 2, 5, 1 }, // base cell 3 + { 0, -1, 1, 0, 3, 4, 2 }, // base cell 4 (pentagon) + { 0, 0, 1, 0, 1, 0, 1 }, // base cell 5 + { 0, 0, 0, 3, 5, 5, 0 }, // base cell 6 + { 0, 0, 0, 0, 0, 5, 0 }, // base cell 7 + { 0, 5, 0, 0, 0, 5, 1 }, // base cell 8 + { 0, 0, 1, 3, 0, 0, 1 }, // base cell 9 + { 0, 0, 1, 3, 0, 0, 1 }, // base cell 10 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 11 + { 0, 5, 0, 0, 3, 5, 1 }, // base cell 12 + { 0, 0, 1, 0, 1, 0, 1 }, // base cell 13 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 14 (pentagon) + { 0, 5, 0, 0, 4, 5, 1 }, // base cell 15 + { 0, 0, 0, 0, 0, 5, 0 }, // base cell 16 + { 0, 3, 3, 3, 3, 0, 3 }, // base cell 17 + { 0, 0, 0, 3, 5, 5, 0 }, // base cell 18 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 19 + { 0, 3, 3, 3, 0, 3, 0 }, // base cell 20 + { 0, 0, 0, 3, 5, 5, 0 }, // base cell 21 + { 0, 0, 1, 0, 1, 0, 1 }, // base cell 22 + { 0, 3, 3, 3, 0, 3, 0 }, // base cell 23 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 24 (pentagon) + { 0, 0, 0, 3, 0, 0, 3 }, // base cell 25 + { 0, 0, 0, 0, 0, 5, 0 }, // base cell 26 + { 0, 3, 0, 0, 0, 3, 3 }, // base cell 27 + { 0, 0, 1, 0, 1, 0, 1 }, // base cell 28 + { 0, 0, 1, 3, 0, 0, 1 }, // base cell 29 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 30 + { 0, 0, 0, 0, 0, 5, 0 }, // base cell 31 + { 0, 3, 3, 3, 3, 0, 3 }, // base cell 32 + { 0, 0, 1, 3, 0, 0, 1 }, // base cell 33 + { 0, 3, 3, 3, 3, 0, 3 }, // base cell 34 + { 0, 0, 3, 0, 3, 0, 3 }, // base cell 35 + { 0, 0, 0, 3, 0, 0, 3 }, // base cell 36 + { 0, 3, 0, 0, 0, 3, 3 }, // base cell 37 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 38 (pentagon) + { 0, 3, 0, 0, 3, 3, 0 }, // base cell 39 + { 0, 3, 0, 0, 3, 3, 0 }, // base cell 40 + { 0, 0, 0, 3, 5, 5, 0 }, // base cell 41 + { 0, 0, 0, 3, 5, 5, 0 }, // base cell 42 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 43 + { 0, 0, 1, 3, 0, 0, 1 }, // base cell 44 + { 0, 0, 3, 0, 0, 3, 3 }, // base cell 45 + { 0, 0, 0, 3, 0, 3, 0 }, // base cell 46 + { 0, 3, 3, 3, 0, 3, 0 }, // base cell 47 + { 0, 3, 3, 3, 0, 3, 0 }, // base cell 48 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 49 (pentagon) + { 0, 0, 0, 3, 0, 0, 3 }, // base cell 50 + { 0, 3, 0, 0, 0, 3, 3 }, // base cell 51 + { 0, 0, 3, 0, 3, 0, 3 }, // base cell 52 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 53 + { 0, 0, 3, 0, 3, 0, 3 }, // base cell 54 + { 0, 0, 3, 0, 0, 3, 3 }, // base cell 55 + { 0, 3, 3, 3, 0, 0, 3 }, // base cell 56 + { 0, 0, 0, 3, 0, 3, 0 }, // base cell 57 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 58 (pentagon) + { 0, 3, 3, 3, 3, 3, 0 }, // base cell 59 + { 0, 3, 3, 3, 3, 3, 0 }, // base cell 60 + { 0, 3, 3, 3, 3, 0, 3 }, // base cell 61 + { 0, 3, 3, 3, 3, 0, 3 }, // base cell 62 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 63 (pentagon) + { 0, 0, 0, 3, 0, 0, 3 }, // base cell 64 + { 0, 3, 3, 3, 0, 3, 0 }, // base cell 65 + { 0, 3, 0, 0, 0, 3, 3 }, // base cell 66 + { 0, 3, 0, 0, 3, 3, 0 }, // base cell 67 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 68 + { 0, 3, 0, 0, 3, 3, 0 }, // base cell 69 + { 0, 0, 3, 0, 0, 3, 3 }, // base cell 70 + { 0, 0, 0, 3, 0, 3, 0 }, // base cell 71 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 72 (pentagon) + { 0, 3, 3, 3, 0, 0, 3 }, // base cell 73 + { 0, 3, 3, 3, 0, 0, 3 }, // base cell 74 + { 0, 0, 0, 3, 0, 0, 3 }, // base cell 75 + { 0, 3, 0, 0, 0, 3, 3 }, // base cell 76 + { 0, 0, 0, 3, 0, 5, 0 }, // base cell 77 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 78 + { 0, 0, 1, 3, 1, 0, 1 }, // base cell 79 + { 0, 0, 1, 3, 1, 0, 1 }, // base cell 80 + { 0, 0, 3, 0, 3, 0, 3 }, // base cell 81 + { 0, 0, 3, 0, 3, 0, 3 }, // base cell 82 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 83 (pentagon) + { 0, 0, 3, 0, 0, 3, 3 }, // base cell 84 + { 0, 0, 0, 3, 0, 3, 0 }, // base cell 85 + { 0, 3, 0, 0, 3, 3, 0 }, // base cell 86 + { 0, 3, 3, 3, 3, 3, 0 }, // base cell 87 + { 0, 0, 0, 3, 0, 5, 0 }, // base cell 88 + { 0, 3, 3, 3, 3, 3, 0 }, // base cell 89 + { 0, 0, 0, 0, 0, 0, 1 }, // base cell 90 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 91 + { 0, 0, 0, 3, 0, 5, 0 }, // base cell 92 + { 0, 5, 0, 0, 5, 5, 0 }, // base cell 93 + { 0, 0, 3, 0, 0, 3, 3 }, // base cell 94 + { 0, 0, 0, 0, 0, 0, 1 }, // base cell 95 + { 0, 0, 0, 3, 0, 3, 0 }, // base cell 96 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 97 (pentagon) + { 0, 3, 3, 3, 0, 0, 3 }, // base cell 98 + { 0, 5, 0, 0, 5, 5, 0 }, // base cell 99 + { 0, 0, 1, 3, 1, 0, 1 }, // base cell 100 + { 0, 3, 3, 3, 0, 0, 3 }, // base cell 101 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 102 + { 0, 0, 1, 3, 1, 0, 1 }, // base cell 103 + { 0, 3, 3, 3, 3, 3, 0 }, // base cell 104 + { 0, 0, 0, 0, 0, 0, 1 }, // base cell 105 + { 0, 0, 1, 0, 3, 5, 1 }, // base cell 106 + { 0, -1, 3, 0, 5, 2, 0 }, // base cell 107 (pentagon) + { 0, 5, 0, 0, 5, 5, 0 }, // base cell 108 + { 0, 0, 1, 0, 4, 5, 1 }, // base cell 109 + { 0, 3, 3, 3, 0, 0, 0 }, // base cell 110 + { 0, 0, 0, 3, 0, 5, 0 }, // base cell 111 + { 0, 0, 0, 3, 0, 5, 0 }, // base cell 112 + { 0, 0, 1, 0, 2, 5, 1 }, // base cell 113 + { 0, 0, 0, 0, 0, 0, 1 }, // base cell 114 + { 0, 0, 1, 3, 1, 0, 1 }, // base cell 115 + { 0, 5, 0, 0, 5, 5, 0 }, // base cell 116 + { 0, -1, 1, 0, 3, 4, 2 }, // base cell 117 (pentagon) + { 0, 0, 1, 0, 0, 5, 1 }, // base cell 118 + { 0, 0, 0, 0, 0, 0, 1 }, // base cell 119 + { 0, 5, 0, 0, 5, 5, 0 }, // base cell 120 + { 0, 0, 1, 0, 1, 5, 1 }, // base cell 121 + }; + + private static final int E_SUCCESS = 0; // Success (no error) + private static final int E_PENTAGON = 9; // Pentagon distortion was encountered which the algorithm + private static final int E_CELL_INVALID = 5; // `H3Index` cell argument was not valid + private static final int E_FAILED = 1; // The operation failed but a more specific error is not available + + /** + * Directions used for traversing a hexagonal ring counterclockwise around + * {1, 0, 0} + * + *

+     *      _
+     *    _/ \\_
+     *   / \\5/ \\
+     *   \\0/ \\4/
+     *   / \\_/ \\
+     *   \\1/ \\3/
+     *     \\2/
+     * 
+ */ + private static final CoordIJK.Direction[] DIRECTIONS = new CoordIJK.Direction[] { + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT }; + + /** + * New digit when traversing along class II grids. + * + * Current digit -> direction -> new digit. + */ + private static final CoordIJK.Direction[][] NEW_DIGIT_II = new CoordIJK.Direction[][] { + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT }, + { + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT }, + { + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT }, + { + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT }, + { + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT }, + { + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT } }; + + /** + * New traversal direction when traversing along class II grids. + * + * Current digit -> direction -> new ap7 move (at coarser level). + */ + private static final CoordIJK.Direction[][] NEW_ADJUSTMENT_II = new CoordIJK.Direction[][] { + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT } }; + + /** + * New traversal direction when traversing along class III grids. + * + * Current digit -> direction -> new ap7 move (at coarser level). + */ + private static final CoordIJK.Direction[][] NEW_DIGIT_III = new CoordIJK.Direction[][] { + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT }, + { + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT }, + { + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT }, + { + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT }, + { + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT }, + { + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT } }; + + /** + * New traversal direction when traversing along class III grids. + * + * Current digit -> direction -> new ap7 move (at coarser level). + */ + private static final CoordIJK.Direction[][] NEW_ADJUSTMENT_III = new CoordIJK.Direction[][] { + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.J_AXES_DIGIT, + CoordIJK.Direction.JK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.K_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.IK_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT }, + { + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.I_AXES_DIGIT, + CoordIJK.Direction.CENTER_DIGIT, + CoordIJK.Direction.IJ_AXES_DIGIT } }; + + /** + * Produce all neighboring cells. For Hexagons there will be 6 neighbors while + * for pentagon just 5. + * Output is placed in the provided array in no particular order. + * + * @param origin origin cell + */ + public static long[] hexRing(long origin) { + final long[] out = H3Index.H3_is_pentagon(origin) ? new long[5] : new long[6]; + int idx = 0; + long previous = -1; + for (int i = 0; i < 6; i++) { + int[] rotations = new int[] { 0 }; + long[] nextNeighbor = new long[] { 0 }; + int neighborResult = h3NeighborRotations(origin, DIRECTIONS[i].digit(), rotations, nextNeighbor); + if (neighborResult != E_PENTAGON) { + // E_PENTAGON is an expected case when trying to traverse off of + // pentagons. + if (neighborResult != E_SUCCESS) { + throw new IllegalArgumentException(); + } + if (previous != nextNeighbor[0]) { + out[idx++] = nextNeighbor[0]; + previous = nextNeighbor[0]; + } + } + } + assert idx == out.length; + return out; + } + + /** + * Returns the hexagon index neighboring the origin, in the direction dir. + * + * Implementation note: The only reachable case where this returns 0 is if the + * origin is a pentagon and the translation is in the k direction. Thus, + * 0 can only be returned if origin is a pentagon. + * + * @param origin Origin index + * @param dir Direction to move in + * @param rotations Number of ccw rotations to perform to reorient the + * translation vector. Will be modified to the new number of + * rotations to perform (such as when crossing a face edge.) + * @param out H3Index of the specified neighbor if succesful + * @return E_SUCCESS on success + */ + private static int h3NeighborRotations(long origin, int dir, int[] rotations, long[] out) { + long current = origin; + + for (int i = 0; i < rotations[0]; i++) { + dir = CoordIJK.rotate60ccw(dir); + } + + int newRotations = 0; + int oldBaseCell = H3Index.H3_get_base_cell(current); + if (oldBaseCell < 0 || oldBaseCell >= Constants.NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } + int oldLeadingDigit = H3Index.h3LeadingNonZeroDigit(current); + + // Adjust the indexing digits and, if needed, the base cell. + int r = H3Index.H3_get_resolution(current) - 1; + while (true) { + if (r == -1) { + current = H3Index.H3_set_base_cell(current, baseCellNeighbors[oldBaseCell][dir]); + newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir]; + + if (H3Index.H3_get_base_cell(current) == INVALID_BASE_CELL) { + // Adjust for the deleted k vertex at the base cell level. + // This edge actually borders a different neighbor. + current = H3Index.H3_set_base_cell(current, baseCellNeighbors[oldBaseCell][CoordIJK.Direction.IK_AXES_DIGIT.digit()]); + newRotations = baseCellNeighbor60CCWRots[oldBaseCell][CoordIJK.Direction.IK_AXES_DIGIT.digit()]; + + // perform the adjustment for the k-subsequence we're skipping + // over. + current = H3Index.h3Rotate60ccw(current); + rotations[0] = rotations[0] + 1; + } + + break; + } else { + int oldDigit = H3Index.H3_get_index_digit(current, r + 1); + int nextDir; + if (oldDigit == CoordIJK.Direction.INVALID_DIGIT.digit()) { + // Only possible on invalid input + return E_CELL_INVALID; + } else if (H3Index.isResolutionClassIII(r + 1)) { + current = H3Index.H3_set_index_digit(current, r + 1, NEW_DIGIT_II[oldDigit][dir].digit()); + nextDir = NEW_ADJUSTMENT_II[oldDigit][dir].digit(); + } else { + current = H3Index.H3_set_index_digit(current, r + 1, NEW_DIGIT_III[oldDigit][dir].digit()); + nextDir = NEW_ADJUSTMENT_III[oldDigit][dir].digit(); + } + + if (nextDir != CoordIJK.Direction.CENTER_DIGIT.digit()) { + dir = nextDir; + r--; + } else { + // No more adjustment to perform + break; + } + } + } + + int newBaseCell = H3Index.H3_get_base_cell(current); + if (BaseCells.isBaseCellPentagon(newBaseCell)) { + boolean alreadyAdjustedKSubsequence = false; + + // force rotation out of missing k-axes sub-sequence + if (H3Index.h3LeadingNonZeroDigit(current) == CoordIJK.Direction.K_AXES_DIGIT.digit()) { + if (oldBaseCell != newBaseCell) { + // in this case, we traversed into the deleted + // k subsequence of a pentagon base cell. + // We need to rotate out of that case depending + // on how we got here. + // check for a cw/ccw offset face; default is ccw + + if (BaseCells.baseCellIsCwOffset(newBaseCell, BaseCells.getBaseFaceIJK(oldBaseCell).face)) { + current = H3Index.h3Rotate60cw(current); + } else { + // See cwOffsetPent in testGridDisk.c for why this is + // unreachable. + current = H3Index.h3Rotate60ccw(current); // LCOV_EXCL_LINE + } + alreadyAdjustedKSubsequence = true; + } else { + // In this case, we traversed into the deleted + // k subsequence from within the same pentagon + // base cell. + if (oldLeadingDigit == CoordIJK.Direction.CENTER_DIGIT.digit()) { + // Undefined: the k direction is deleted from here + return E_PENTAGON; + } else if (oldLeadingDigit == CoordIJK.Direction.JK_AXES_DIGIT.digit()) { + // Rotate out of the deleted k subsequence + // We also need an additional change to the direction we're + // moving in + current = H3Index.h3Rotate60ccw(current); + rotations[0] = rotations[0] + 1; + } else if (oldLeadingDigit == CoordIJK.Direction.IK_AXES_DIGIT.digit()) { + // Rotate out of the deleted k subsequence + // We also need an additional change to the direction we're + // moving in + current = H3Index.h3Rotate60cw(current); + rotations[0] = rotations[0] + 5; + } else { + // Should never occur + return E_FAILED; // LCOV_EXCL_LINE + } + } + } + + for (int i = 0; i < newRotations; i++) + current = H3Index.h3RotatePent60ccw(current); + + // Account for differing orientation of the base cells (this edge + // might not follow properties of some other edges.) + if (oldBaseCell != newBaseCell) { + if (BaseCells.isBaseCellPolarPentagon(newBaseCell)) { + // 'polar' base cells behave differently because they have all + // i neighbors. + if (oldBaseCell != 118 + && oldBaseCell != 8 + && H3Index.h3LeadingNonZeroDigit(current) != CoordIJK.Direction.JK_AXES_DIGIT.digit()) { + rotations[0] = rotations[0] + 1; + } + } else if (H3Index.h3LeadingNonZeroDigit(current) == CoordIJK.Direction.IK_AXES_DIGIT.digit() + && alreadyAdjustedKSubsequence == false) { + // account for distortion introduced to the 5 neighbor by the + // deleted k subsequence. + rotations[0] = rotations[0] + 1; + } + } + } else { + for (int i = 0; i < newRotations; i++) + current = H3Index.h3Rotate60ccw(current); + } + + rotations[0] = (rotations[0] + newRotations) % 6; + out[0] = current; + + return E_SUCCESS; + } + +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/Iterator.java b/libs/h3/src/main/java/org/elasticsearch/h3/Iterator.java new file mode 100644 index 0000000000000..d711c3e4224a6 --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/Iterator.java @@ -0,0 +1,306 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2021 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** + * Iterator structures and functions for the children of a cell. + */ +final class Iterator { + /** + * Invalid index used to indicate an error from latLngToCell and related + * functions or missing data in arrays of H3 indices. Analogous to NaN in + * floating point. + */ + public static final long H3_NULL = 0; + + /** + * The number of bits in a single H3 resolution digit. + */ + private static final int H3_PER_DIGIT_OFFSET = 3; + + /** + * IterCellsChildren: struct for iterating through the descendants of + * a given cell. + *

+ * Constructors: + *

+ * Initialize with either `iterInitParent` or `iterInitBaseCellNum`. + * `iterInitParent` sets up an iterator for all the children of a given + * parent cell at a given resolution. + *

+ * `iterInitBaseCellNum` sets up an iterator for children cells, given + * a base cell number (0--121). + *

+ * Iteration: + *

+ * Step iterator with `iterStepChild`. + * During the lifetime of the `IterCellsChildren`, the current iterate + * is accessed via the `IterCellsChildren.h` member. + * When the iterator is exhausted or if there was an error in initialization, + * `IterCellsChildren.h` will be `H3_NULL` even after calling `iterStepChild`. + */ + static class IterCellsChildren { + long h; + int _parentRes; // parent resolution + int _skipDigit; // this digit skips `1` for pentagons + + IterCellsChildren(long h, int _parentRes, int _skipDigit) { + this.h = h; + this._parentRes = _parentRes; + this._skipDigit = _skipDigit; + } + } + + /** + * Create a fully nulled-out child iterator for when an iterator is exhausted. + * This helps minimize the chance that a user will depend on the iterator + * internal state after it's exhausted, like the child resolution, for + * example. + */ + private static IterCellsChildren nullIter() { + return new IterCellsChildren(H3_NULL, -1, -1); + } + + /** + ## Logic for iterating through the children of a cell + We'll describe the logic for .... + - normal (non pentagon iteration) + - pentagon iteration. define "pentagon digit" + ### Cell Index Component Diagrams + The lower 56 bits of an H3 Cell Index describe the following index components: + - the cell resolution (4 bits) + - the base cell number (7 bits) + - the child cell digit for each resolution from 1 to 15 (3*15 = 45 bits) + These are the bits we'll be focused on when iterating through child cells. + To help describe the iteration logic, we'll use diagrams displaying the + (decimal) values for each component like: + child digit for resolution 2 + / + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | ... | + |-----|-------------|---|---|---|---|---|---|-----| + | 9 | 17 | 5 | 3 | 0 | 6 | 2 | 1 | ... | + ### Iteration through children of a hexagon (but not a pentagon) + Iteration through the children of a *hexagon* (but not a pentagon) + simply involves iterating through all the children values (0--6) + for each child digit (up to the child's resolution). + For example, suppose a resolution 3 hexagon index has the following + components: + parent resolution + / + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | ... | + |-----|-------------|---|---|---|---|---|---|-----| + | 3 | 17 | 3 | 5 | 1 | 7 | 7 | 7 | ... | + The iteration through all children of resolution 6 would look like: + parent res child res + / / + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... | + |-----|-------------|---|---|---|---|---|---|---|---|-----| + | 6 | 17 | 3 | 5 | 1 | 0 | 0 | 0 | 7 | 7 | ... | + | 6 | 17 | 3 | 5 | 1 | 0 | 0 | 1 | 7 | 7 | ... | + | ... | | | | | | | | | | | + | 6 | 17 | 3 | 5 | 1 | 0 | 0 | 6 | 7 | 7 | ... | + | 6 | 17 | 3 | 5 | 1 | 0 | 1 | 0 | 7 | 7 | ... | + | 6 | 17 | 3 | 5 | 1 | 0 | 1 | 1 | 7 | 7 | ... | + | ... | | | | | | | | | | | + | 6 | 17 | 3 | 5 | 1 | 6 | 6 | 6 | 7 | 7 | ... | + ### Step sequence on a *pentagon* cell + Pentagon cells have a base cell number (e.g., 97) corresponding to a + resolution 0 pentagon, and have all zeros from digit 1 to the digit + corresponding to the cell's resolution. + (We'll drop the ellipses from now on, knowing that digits should contain + 7's beyond the cell resolution.) + parent res child res + / / + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | + |-----|-------------|---|---|---|---|---|---| + | 6 | 97 | 0 | 0 | 0 | 0 | 0 | 0 | + Iteration through children of a *pentagon* is almost the same + as *hexagon* iteration, except that we skip the *first* 1 value + that appears in the "skip digit". This corresponds to the fact + that a pentagon only has 6 children, which are denoted with + the numbers {0,2,3,4,5,6}. + The skip digit starts at the child resolution position. + When iterating through children more than one resolution below + the parent, we move the skip digit to the left + (up to the next coarser resolution) each time we skip the 1 value + in that digit. + Iteration would start like: + parent res child res + / / + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | + |-----|-------------|---|---|---|---|---|---| + | 6 | 97 | 0 | 0 | 0 | 0 | 0 | 0 | + \ + skip digit + Noticing we skip the 1 value and move the skip digit, + the next iterate would be: + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | + |-----|-------------|---|---|---|---|---|---| + | 6 | 97 | 0 | 0 | 0 | 0 | 0 | 2 | + \ + skip digit + Iteration continues normally until we get to: + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | + |-----|-------------|---|---|---|---|---|---| + | 6 | 97 | 0 | 0 | 0 | 0 | 0 | 6 | + \ + skip digit + which is followed by (skipping the 1): + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | + |-----|-------------|---|---|---|---|---|---| + | 6 | 97 | 0 | 0 | 0 | 0 | 2 | 0 | + \ + skip digit + For the next iterate, we won't skip the `1` in the previous digit + because it is no longer the skip digit: + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | + |-----|-------------|---|---|---|---|---|---| + | 6 | 97 | 0 | 0 | 0 | 0 | 2 | 1 | + \ + skip digit + Iteration continues normally until we're right before the next skip + digit: + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | + |-----|-------------|---|---|---|---|---|---| + | 6 | 97 | 0 | 0 | 0 | 0 | 6 | 6 | + \ + skip digit + Which is followed by + | res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | + |-----|-------------|---|---|---|---|---|---| + | 6 | 97 | 0 | 0 | 0 | 2 | 0 | 0 | + \ + skip digit + and so on. + */ + + /** + * Initialize a IterCellsChildren struct representing the sequence giving + * the children of cell `h` at resolution `childRes`. + *

+ * At any point in the iteration, starting once + * the struct is initialized, IterCellsChildren.h gives the current child. + *

+ * Also, IterCellsChildren.h == H3_NULL when all the children have been iterated + * through, or if the input to `iterInitParent` was invalid. + */ + public static IterCellsChildren iterInitParent(long h, int childRes) { + + int parentRes = H3Index.H3_get_resolution(h); + + if (childRes < parentRes || childRes > Constants.MAX_H3_RES || h == H3_NULL) { + return nullIter(); + } + + long newH = zeroIndexDigits(h, parentRes + 1, childRes); + newH = H3Index.H3_set_resolution(newH, childRes); + + int _skipDigit; + if (H3Index.H3_is_pentagon(newH)) { + // The skip digit skips `1` for pentagons. + // The "_skipDigit" moves to the left as we count up from the + // child resolution to the parent resolution. + _skipDigit = childRes; + } else { + // if not a pentagon, we can ignore "skip digit" logic + _skipDigit = -1; + } + + return new IterCellsChildren(newH, parentRes, _skipDigit); + } + + /** + * Step a IterCellsChildren to the next child cell. + * When the iteration is over, IterCellsChildren.h will be H3_NULL. + * Handles iterating through hexagon and pentagon cells. + */ + public static void iterStepChild(IterCellsChildren it) { + // once h == H3_NULL, the iterator returns an infinite sequence of H3_NULL + if (it.h == H3_NULL) return; + + int childRes = H3Index.H3_get_resolution(it.h); + + incrementResDigit(it, childRes); + + for (int i = childRes; i >= it._parentRes; i--) { + if (i == it._parentRes) { + // if we're modifying the parent resolution digit, then we're done + // *it = _null_iter(); + it.h = H3_NULL; + return; + } + + // PENTAGON_SKIPPED_DIGIT == 1 + if (i == it._skipDigit && getResDigit(it, i) == CoordIJK.Direction.PENTAGON_SKIPPED_DIGIT.digit()) { + // Then we are iterating through the children of a pentagon cell. + // All children of a pentagon have the property that the first + // nonzero digit between the parent and child resolutions is + // not 1. + // I.e., we never see a sequence like 00001. + // Thus, we skip the `1` in this digit. + incrementResDigit(it, i); + it._skipDigit -= 1; + return; + } + + // INVALID_DIGIT == 7 + if (getResDigit(it, i) == CoordIJK.Direction.INVALID_DIGIT.digit()) { + incrementResDigit(it, i); // zeros out it[i] and increments it[i-1] by 1 + } else { + break; + } + } + } + + // extract the `res` digit (0--7) of the current cell + private static int getResDigit(IterCellsChildren it, int res) { + return H3Index.H3_get_index_digit(it.h, res); + } + + /** + * Zero out index digits from start to end, inclusive. + * No-op if start > end. + */ + private static long zeroIndexDigits(long h, int start, int end) { + if (start > end) { + return h; + } + + long m = 0; + + m = ~m; + m <<= H3_PER_DIGIT_OFFSET * (end - start + 1); + m = ~m; + m <<= H3_PER_DIGIT_OFFSET * (Constants.MAX_H3_RES - end); + m = ~m; + + return h & m; + } + + // increment the digit (0--7) at location `res` + private static void incrementResDigit(IterCellsChildren it, int res) { + long val = 1; + val <<= H3_PER_DIGIT_OFFSET * (Constants.MAX_H3_RES - res); + it.h += val; + } +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/LatLng.java b/libs/h3/src/main/java/org/elasticsearch/h3/LatLng.java new file mode 100644 index 0000000000000..4a78a4b98f566 --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/LatLng.java @@ -0,0 +1,121 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2021 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +/** pair of latitude/longitude */ +public final class LatLng { + + // lat / lon in radians + private final double lon; + private final double lat; + + LatLng(double lat, double lon) { + this.lon = lon; + this.lat = lat; + } + + /** Returns latitude in radians */ + public double getLatRad() { + return lat; + } + + /** Returns longitude in radians */ + public double getLonRad() { + return lon; + } + + /** Returns latitude in degrees */ + public double getLatDeg() { + return Math.toDegrees(getLatRad()); + } + + /** Returns longitude in degrees */ + public double getLonDeg() { + return Math.toDegrees(getLonRad()); + } + + /** + * Encodes a coordinate on the sphere to the corresponding icosahedral face and + * containing 2D hex coordinates relative to that face center. + * + * @param res The desired H3 resolution for the encoding. + */ + FaceIJK geoToFaceIJK(int res) { + Vec3d v3d = new Vec3d(this); + + // determine the icosahedron face + int face = 0; + double sqd = v3d.pointSquareDist(Vec3d.faceCenterPoint[0]); + for (int i = 1; i < Vec3d.faceCenterPoint.length; i++) { + double sqdT = v3d.pointSquareDist(Vec3d.faceCenterPoint[i]); + if (sqdT < sqd) { + face = i; + sqd = sqdT; + } + } + // cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2 + double r = Math.acos(1 - sqd / 2); + + if (r < Constants.EPSILON) { + return new FaceIJK(face, new Vec2d(0.0, 0.0).hex2dToCoordIJK()); + } + + // now have face and r, now find CCW theta from CII i-axis + double theta = Vec2d.posAngleRads( + Vec2d.faceAxesAzRadsCII[face][0] - Vec2d.posAngleRads(Vec2d.faceCenterGeo[face].geoAzimuthRads(this)) + ); + + // adjust theta for Class III (odd resolutions) + if (H3Index.isResolutionClassIII(res)) { + theta = Vec2d.posAngleRads(theta - Constants.M_AP7_ROT_RADS); + } + + // perform gnomonic scaling of r + r = Math.tan(r); + + // scale for current resolution length u + r /= Constants.RES0_U_GNOMONIC; + for (int i = 0; i < res; i++) { + r *= Constants.M_SQRT7; + } + + // we now have (r, theta) in hex2d with theta ccw from x-axes + + // convert to local x,y + Vec2d vec2d = new Vec2d(r * Math.cos(theta), r * Math.sin(theta)); + return new FaceIJK(face, vec2d.hex2dToCoordIJK()); + } + + /** + * Determines the azimuth to the provided LatLng in radians. + * + * @param p The spherical coordinates. + * @return The azimuth in radians. + */ + private double geoAzimuthRads(LatLng p) { + return Math.atan2( + Math.cos(p.lat) * Math.sin(p.lon - lon), + Math.cos(lat) * Math.sin(p.lat) - Math.sin(lat) * Math.cos(p.lat) * Math.cos(p.lon - lon) + ); + } +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/Vec2d.java b/libs/h3/src/main/java/org/elasticsearch/h3/Vec2d.java new file mode 100644 index 0000000000000..de609e4ba9e0a --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/Vec2d.java @@ -0,0 +1,404 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2016-2017 Uber Technologies, Inc. + */ +package org.elasticsearch.h3; + +import java.util.Objects; + +/** + * 2D floating-point vector + */ +final class Vec2d { + + /** sin(60') */ + private static final double M_SIN60 = Constants.M_SQRT3_2; + + /** + * icosahedron face centers in lat/lng radians + */ + public static final LatLng[] faceCenterGeo = new LatLng[] { + new LatLng(0.803582649718989942, 1.248397419617396099), // face 0 + new LatLng(1.307747883455638156, 2.536945009877921159), // face 1 + new LatLng(1.054751253523952054, -1.347517358900396623), // face 2 + new LatLng(0.600191595538186799, -0.450603909469755746), // face 3 + new LatLng(0.491715428198773866, 0.401988202911306943), // face 4 + new LatLng(0.172745327415618701, 1.678146885280433686), // face 5 + new LatLng(0.605929321571350690, 2.953923329812411617), // face 6 + new LatLng(0.427370518328979641, -1.888876200336285401), // face 7 + new LatLng(-0.079066118549212831, -0.733429513380867741), // face 8 + new LatLng(-0.230961644455383637, 0.506495587332349035), // face 9 + new LatLng(0.079066118549212831, 2.408163140208925497), // face 10 + new LatLng(0.230961644455383637, -2.635097066257444203), // face 11 + new LatLng(-0.172745327415618701, -1.463445768309359553), // face 12 + new LatLng(-0.605929321571350690, -0.187669323777381622), // face 13 + new LatLng(-0.427370518328979641, 1.252716453253507838), // face 14 + new LatLng(-0.600191595538186799, 2.690988744120037492), // face 15 + new LatLng(-0.491715428198773866, -2.739604450678486295), // face 16 + new LatLng(-0.803582649718989942, -1.893195233972397139), // face 17 + new LatLng(-1.307747883455638156, -0.604647643711872080), // face 18 + new LatLng(-1.054751253523952054, 1.794075294689396615), // face 19 + }; + + /** + * icosahedron face ijk axes as azimuth in radians from face center to + * vertex 0/1/2 respectively + */ + public static final double[][] faceAxesAzRadsCII = new double[][] { + { 5.619958268523939882, 3.525563166130744542, 1.431168063737548730 }, // face 0 + { 5.760339081714187279, 3.665943979320991689, 1.571548876927796127 }, // face 1 + { 0.780213654393430055, 4.969003859179821079, 2.874608756786625655 }, // face 2 + { 0.430469363979999913, 4.619259568766391033, 2.524864466373195467 }, // face 3 + { 6.130269123335111400, 4.035874020941915804, 1.941478918548720291 }, // face 4 + { 2.692877706530642877, 0.598482604137447119, 4.787272808923838195 }, // face 5 + { 2.982963003477243874, 0.888567901084048369, 5.077358105870439581 }, // face 6 + { 3.532912002790141181, 1.438516900396945656, 5.627307105183336758 }, // face 7 + { 3.494305004259568154, 1.399909901866372864, 5.588700106652763840 }, // face 8 + { 3.003214169499538391, 0.908819067106342928, 5.097609271892733906 }, // face 9 + { 5.930472956509811562, 3.836077854116615875, 1.741682751723420374 }, // face 10 + { 0.138378484090254847, 4.327168688876645809, 2.232773586483450311 }, // face 11 + { 0.448714947059150361, 4.637505151845541521, 2.543110049452346120 }, // face 12 + { 0.158629650112549365, 4.347419854898940135, 2.253024752505744869 }, // face 13 + { 5.891865957979238535, 3.797470855586042958, 1.703075753192847583 }, // face 14 + { 2.711123289609793325, 0.616728187216597771, 4.805518392002988683 }, // face 15 + { 3.294508837434268316, 1.200113735041072948, 5.388903939827463911 }, // face 16 + { 3.804819692245439833, 1.710424589852244509, 5.899214794638635174 }, // face 17 + { 3.664438879055192436, 1.570043776661997111, 5.758833981448388027 }, // face 18 + { 2.361378999196363184, 0.266983896803167583, 4.455774101589558636 }, // face 19 + }; + + /** + * pi + */ + private static double M_PI = 3.14159265358979323846; + /** + * pi / 2.0 + */ + private static double M_PI_2 = 1.5707963267948966; + /** + * 2.0 * PI + */ + public static double M_2PI = 6.28318530717958647692528676655900576839433; + + private final double x; /// < x component + private final double y; /// < y component + + Vec2d(double x, double y) { + this.x = x; + this.y = y; + } + + /** + * Determines the center point in spherical coordinates of a cell given by 2D + * hex coordinates on a particular icosahedral face. + * + * @param face The icosahedral face upon which the 2D hex coordinate system is + * centered. + * @param res The H3 resolution of the cell. + * @param substrate Indicates whether or not this grid is actually a substrate + * grid relative to the specified resolution. + */ + public LatLng hex2dToGeo(int face, int res, boolean substrate) { + // calculate (r, theta) in hex2d + double r = v2dMag(); + + if (r < Constants.EPSILON) { + return faceCenterGeo[face]; + } + + double theta = Math.atan2(y, x); + + // scale for current resolution length u + for (int i = 0; i < res; i++) { + r /= Constants.M_SQRT7; + } + + // scale accordingly if this is a substrate grid + if (substrate) { + r /= 3.0; + if (H3Index.isResolutionClassIII(res)) { + r /= Constants.M_SQRT7; + } + } + + r *= Constants.RES0_U_GNOMONIC; + + // perform inverse gnomonic scaling of r + r = Math.atan(r); + + // adjust theta for Class III + // if a substrate grid, then it's already been adjusted for Class III + if (substrate == false && H3Index.isResolutionClassIII(res)) theta = posAngleRads(theta + Constants.M_AP7_ROT_RADS); + + // find theta as an azimuth + theta = posAngleRads(faceAxesAzRadsCII[face][0] - theta); + + // now find the point at (r,theta) from the face center + return geoAzDistanceRads(faceCenterGeo[face], theta, r); + } + + /** + * Determine the containing hex in ijk+ coordinates for a 2D cartesian + * coordinate vector (from DGGRID). + * + */ + public CoordIJK hex2dToCoordIJK() { + double a1, a2; + double x1, x2; + int m1, m2; + double r1, r2; + + // quantize into the ij system and then normalize + int k = 0; + int i; + int j; + + a1 = Math.abs(x); + a2 = Math.abs(y); + + // first do a reverse conversion + x2 = a2 / M_SIN60; + x1 = a1 + x2 / 2.0; + + // check if we have the center of a hex + m1 = (int) x1; + m2 = (int) x2; + + // otherwise round correctly + r1 = x1 - m1; + r2 = x2 - m2; + + if (r1 < 0.5) { + if (r1 < 1.0 / 3.0) { + if (r2 < (1.0 + r1) / 2.0) { + i = m1; + j = m2; + } else { + i = m1; + j = m2 + 1; + } + } else { + if (r2 < (1.0 - r1)) { + j = m2; + } else { + j = m2 + 1; + } + + if ((1.0 - r1) <= r2 && r2 < (2.0 * r1)) { + i = m1 + 1; + } else { + i = m1; + } + } + } else { + if (r1 < 2.0 / 3.0) { + if (r2 < (1.0 - r1)) { + j = m2; + } else { + j = m2 + 1; + } + + if ((2.0 * r1 - 1.0) < r2 && r2 < (1.0 - r1)) { + i = m1; + } else { + i = m1 + 1; + } + } else { + if (r2 < (r1 / 2.0)) { + i = m1 + 1; + j = m2; + } else { + i = m1 + 1; + j = m2 + 1; + } + } + } + + // now fold across the axes if necessary + + if (x < 0.0) { + if ((j % 2) == 0) // even + { + int axisi = j / 2; + int diff = i - axisi; + i = i - 2 * diff; + } else { + int axisi = (j + 1) / 2; + int diff = i - axisi; + i = i - (2 * diff + 1); + } + } + + if (y < 0.0) { + i = i - (2 * j + 1) / 2; + j = -1 * j; + } + CoordIJK coordIJK = new CoordIJK(i, j, k); + coordIJK.ijkNormalize(); + return coordIJK; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Vec2d vec2d = (Vec2d) o; + return Double.compare(vec2d.x, x) == 0 && Double.compare(vec2d.y, y) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + /** + * Finds the intersection between two lines. Assumes that the lines intersect + * and that the intersection is not at an endpoint of either line. + * + * @param p0 The first endpoint of the first line. + * @param p1 The second endpoint of the first line. + * @param p2 The first endpoint of the second line. + * @param p3 The second endpoint of the second line. + */ + public static Vec2d v2dIntersect(Vec2d p0, Vec2d p1, Vec2d p2, Vec2d p3) { + double[] s1 = new double[2], s2 = new double[2]; + s1[0] = p1.x - p0.x; + s1[1] = p1.y - p0.y; + s2[0] = p3.x - p2.x; + s2[1] = p3.y - p2.y; + + float t; + t = (float) ((s2[0] * (p0.y - p2.y) - s2[1] * (p0.x - p2.x)) / (-s2[0] * s1[1] + s1[0] * s2[1])); + + return new Vec2d(p0.x + (t * s1[0]), p0.y + (t * s1[1])); + } + + /** + * Calculates the magnitude of a 2D cartesian vector. + * + * @return The magnitude of the vector. + */ + private double v2dMag() { + return Math.sqrt(x * x + y * y); + } + + /** + * Normalizes radians to a value between 0.0 and two PI. + * + * @param rads The input radians value. + * @return The normalized radians value. + */ + static double posAngleRads(double rads) { + double tmp = ((rads < 0.0) ? rads + M_2PI : rads); + if (rads >= M_2PI) tmp -= M_2PI; + return tmp; + } + + /** + * Computes the point on the sphere a specified azimuth and distance from + * another point. + * + * @param p1 The first spherical coordinates. + * @param az The desired azimuth from p1. + * @param distance The desired distance from p1, must be non-negative. + * p1. + */ + private static LatLng geoAzDistanceRads(LatLng p1, double az, double distance) { + if (distance < Constants.EPSILON) { + return p1; + } + + double sinlat, sinlng, coslng; + + az = posAngleRads(az); + + double lat, lon; + + // check for due north/south azimuth + if (az < Constants.EPSILON || Math.abs(az - M_PI) < Constants.EPSILON) { + if (az < Constants.EPSILON) {// due north + lat = p1.getLatRad() + distance; + } else { // due south + lat = p1.getLatRad() - distance; + } + if (Math.abs(lat - M_PI_2) < Constants.EPSILON) { // north pole + lat = M_PI_2; + lon = 0.0; + } else if (Math.abs(lat + M_PI_2) < Constants.EPSILON) { // south pole + lat = -M_PI_2; + lon = 0.0; + } else { + lon = constrainLng(p1.getLonRad()); + } + } else { // not due north or south + sinlat = Math.sin(p1.getLatRad()) * Math.cos(distance) + Math.cos(p1.getLatRad()) * Math.sin(distance) * Math.cos(az); + if (sinlat > 1.0) { + sinlat = 1.0; + } + if (sinlat < -1.0) { + sinlat = -1.0; + } + lat = Math.asin(sinlat); + if (Math.abs(lat - M_PI_2) < Constants.EPSILON) // north pole + { + lat = M_PI_2; + lon = 0.0; + } else if (Math.abs(lat + M_PI_2) < Constants.EPSILON) // south pole + { + lat = -M_PI_2; + lon = 0.0; + } else { + sinlng = Math.sin(az) * Math.sin(distance) / Math.cos(lat); + coslng = (Math.cos(distance) - Math.sin(p1.getLatRad()) * Math.sin(lat)) / Math.cos(p1.getLatRad()) / Math.cos(lat); + if (sinlng > 1.0) { + sinlng = 1.0; + } + if (sinlng < -1.0) { + sinlng = -1.0; + } + if (coslng > 1.0) { + coslng = 1.0; + } + if (coslng < -1.0) { + coslng = -1.0; + } + lon = constrainLng(p1.getLonRad() + Math.atan2(sinlng, coslng)); + } + } + return new LatLng(lat, lon); + } + + /** + * constrainLng makes sure longitudes are in the proper bounds + * + * @param lng The origin lng value + * @return The corrected lng value + */ + private static double constrainLng(double lng) { + while (lng > M_PI) { + lng = lng - (2 * M_PI); + } + while (lng < -M_PI) { + lng = lng + (2 * M_PI); + } + return lng; + } +} diff --git a/libs/h3/src/main/java/org/elasticsearch/h3/Vec3d.java b/libs/h3/src/main/java/org/elasticsearch/h3/Vec3d.java new file mode 100644 index 0000000000000..605ecc09a606a --- /dev/null +++ b/libs/h3/src/main/java/org/elasticsearch/h3/Vec3d.java @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License. + * + * Copyright 2018, 2020-2021 Uber Technologies, Inc. + */ + +package org.elasticsearch.h3; + +final class Vec3d { + + /** icosahedron face centers in x/y/z on the unit sphere */ + public static final double[][] faceCenterPoint = new double[][] { + { 0.2199307791404606, 0.6583691780274996, 0.7198475378926182 }, // face 0 + { -0.2139234834501421, 0.1478171829550703, 0.9656017935214205 }, // face 1 + { 0.1092625278784797, -0.4811951572873210, 0.8697775121287253 }, // face 2 + { 0.7428567301586791, -0.3593941678278028, 0.5648005936517033 }, // face 3 + { 0.8112534709140969, 0.3448953237639384, 0.4721387736413930 }, // face 4 + { -0.1055498149613921, 0.9794457296411413, 0.1718874610009365 }, // face 5 + { -0.8075407579970092, 0.1533552485898818, 0.5695261994882688 }, // face 6 + { -0.2846148069787907, -0.8644080972654206, 0.4144792552473539 }, // face 7 + { 0.7405621473854482, -0.6673299564565524, -0.0789837646326737 }, // face 8 + { 0.8512303986474293, 0.4722343788582681, -0.2289137388687808 }, // face 9 + { -0.7405621473854481, 0.6673299564565524, 0.0789837646326737 }, // face 10 + { -0.8512303986474292, -0.4722343788582682, 0.2289137388687808 }, // face 11 + { 0.1055498149613919, -0.9794457296411413, -0.1718874610009365 }, // face 12 + { 0.8075407579970092, -0.1533552485898819, -0.5695261994882688 }, // face 13 + { 0.2846148069787908, 0.8644080972654204, -0.4144792552473539 }, // face 14 + { -0.7428567301586791, 0.3593941678278027, -0.5648005936517033 }, // face 15 + { -0.8112534709140971, -0.3448953237639382, -0.4721387736413930 }, // face 16 + { -0.2199307791404607, -0.6583691780274996, -0.7198475378926182 }, // face 17 + { 0.2139234834501420, -0.1478171829550704, -0.9656017935214205 }, // face 18 + { -0.1092625278784796, 0.4811951572873210, -0.8697775121287253 }, // face 19 + }; + + private final double x; + private final double y; + private final double z; + + Vec3d(LatLng latLng) { + double r = Math.cos(latLng.getLatRad()); + this.z = Math.sin(latLng.getLatRad()); + this.x = Math.cos(latLng.getLonRad()) * r; + this.y = Math.sin(latLng.getLonRad()) * r; + } + + /** + * Calculate the square of the distance between two 3D coordinates. + * + * @param v The first 3D coordinate. + * @return The square of the distance between the given points. + */ + public double pointSquareDist(double[] v) { + return square(x - v[0]) + square(y - v[1]) + square(z - v[2]); + } + + /** + * Square of a number + * + * @param x The input number. + * @return The square of the input number. + */ + private double square(double x) { + return x * x; + } + +} diff --git a/libs/h3/src/test/java/org/elasticsearch/h3/CellBoundaryTests.java b/libs/h3/src/test/java/org/elasticsearch/h3/CellBoundaryTests.java new file mode 100644 index 0000000000000..7671f1c1a3510 --- /dev/null +++ b/libs/h3/src/test/java/org/elasticsearch/h3/CellBoundaryTests.java @@ -0,0 +1,178 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.h3; + +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.elasticsearch.test.ESTestCase; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +public class CellBoundaryTests extends ESTestCase { + + public void testRes0() throws Exception { + processFile("res00cells.txt"); + } + + public void testRes1() throws Exception { + processFile("res01cells.txt"); + } + + public void testRes2() throws Exception { + processFile("res02cells.txt"); + } + + public void testRes3() throws Exception { + processFile("res03cells.txt"); + } + + public void testBc05r08cells() throws Exception { + processFile("bc05r08cells.txt"); + } + + public void testBc05r09cells() throws Exception { + processFile("bc05r09cells.txt"); + } + + public void testBc05r10cells() throws Exception { + processFile("bc05r10cells.txt"); + } + + public void testBc05r11cells() throws Exception { + processFile("bc05r11cells.txt"); + } + + public void testBc05r12cells() throws Exception { + processFile("bc05r12cells.txt"); + } + + public void testBc05r13cells() throws Exception { + processFile("bc05r13cells.txt"); + } + + public void testBc05r05cells() throws Exception { + processFile("bc05r14cells.txt"); + } + + public void testBc05r15cells() throws Exception { + processFile("bc05r15cells.txt"); + } + + public void testBc14r08cells() throws Exception { + processFile("bc14r08cells.txt"); + } + + public void testBc14r09cells() throws Exception { + processFile("bc14r09cells.txt"); + } + + public void testBc14r10cells() throws Exception { + processFile("bc14r10cells.txt"); + } + + public void testBc14r11cells() throws Exception { + processFile("bc14r11cells.txt"); + } + + public void testBc14r12cells() throws Exception { + processFile("bc14r12cells.txt"); + } + + public void testBc14r13cells() throws Exception { + processFile("bc14r13cells.txt"); + } + + public void testBc14r14cells() throws Exception { + processFile("bc14r14cells.txt"); + } + + public void testBc14r15cells() throws Exception { + processFile("bc14r15cells.txt"); + } + + public void testBc19r08cells() throws Exception { + processFile("bc19r08cells.txt"); + } + + public void testBc19r09cells() throws Exception { + processFile("bc19r09cells.txt"); + } + + public void testBc19r10cells() throws Exception { + processFile("bc19r10cells.txt"); + } + + public void testBc19r11cells() throws Exception { + processFile("bc19r11cells.txt"); + } + + public void testBc19r12cells() throws Exception { + processFile("bc19r12cells.txt"); + } + + public void testBc19r13cells() throws Exception { + processFile("bc19r13cells.txt"); + } + + public void testBc19r14cells() throws Exception { + processFile("bc19r14cells.txt"); + } + + private void processFile(String file) throws IOException { + InputStream fis = getClass().getResourceAsStream(file + ".gz"); + BufferedReader reader = new BufferedReader(new InputStreamReader(new GzipCompressorInputStream(fis), StandardCharsets.UTF_8)); + String h3Address = reader.readLine(); + while (h3Address != null) { + assertEquals(true, H3.h3IsValid(h3Address)); + long h3 = H3.stringToH3(h3Address); + assertEquals(true, H3.h3IsValid(h3)); + processOne(h3Address, reader); + h3Address = reader.readLine(); + } + } + + private void processOne(String h3Address, BufferedReader reader) throws IOException { + String line = reader.readLine(); + if ("{".equals(line) == false) { + throw new IllegalArgumentException(); + } + line = reader.readLine(); + List points = new ArrayList<>(); + while ("}".equals(line) == false) { + StringTokenizer tokens = new StringTokenizer(line, " "); + assertEquals(2, tokens.countTokens()); + double lat = Double.parseDouble(tokens.nextToken()); + double lon = Double.parseDouble(tokens.nextToken()); + points.add(new double[] { lat, lon }); + line = reader.readLine(); + } + CellBoundary boundary = H3.h3ToGeoBoundary(h3Address); + assert boundary.numPoints() == points.size(); + for (int i = 0; i < boundary.numPoints(); i++) { + assertEquals(h3Address, points.get(i)[0], boundary.getLatLon(i).getLatDeg(), 1e-8); + assertEquals(h3Address, points.get(i)[1], boundary.getLatLon(i).getLonDeg(), 1e-8); + } + } +} diff --git a/libs/h3/src/test/java/org/elasticsearch/h3/CellCenterTests.java b/libs/h3/src/test/java/org/elasticsearch/h3/CellCenterTests.java new file mode 100644 index 0000000000000..16d605b55b2ac --- /dev/null +++ b/libs/h3/src/test/java/org/elasticsearch/h3/CellCenterTests.java @@ -0,0 +1,183 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.h3; + +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.elasticsearch.test.ESTestCase; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.StringTokenizer; + +public class CellCenterTests extends ESTestCase { + + public void testRes0() throws Exception { + processFile("res00ic.txt"); + } + + public void testRes1() throws Exception { + processFile("res01ic.txt"); + } + + public void testRes2() throws Exception { + processFile("res02ic.txt"); + } + + public void testRes3() throws Exception { + processFile("res03ic.txt"); + } + + public void testBc05r08centers() throws Exception { + processFile("bc05r08centers.txt"); + } + + public void testBc05r09centers() throws Exception { + processFile("bc05r09centers.txt"); + } + + public void testBc05r10centers() throws Exception { + processFile("bc05r10centers.txt"); + } + + public void testBc05r11centers() throws Exception { + processFile("bc05r11centers.txt"); + } + + public void testBc05r12centers() throws Exception { + processFile("bc05r12centers.txt"); + } + + public void testBc05r13centers() throws Exception { + processFile("bc05r13centers.txt"); + } + + public void testBc05r05centers() throws Exception { + processFile("bc05r14centers.txt"); + } + + public void testBc05r15centers() throws Exception { + processFile("bc05r15centers.txt"); + } + + public void testBc14r08centers() throws Exception { + processFile("bc14r08centers.txt"); + } + + public void testBc14r09centers() throws Exception { + processFile("bc14r09centers.txt"); + } + + public void testBc14r10centers() throws Exception { + processFile("bc14r10centers.txt"); + } + + public void testBc14r11centers() throws Exception { + processFile("bc14r11centers.txt"); + } + + public void testBc14r12centers() throws Exception { + processFile("bc14r12centers.txt"); + } + + public void testBc14r13centers() throws Exception { + processFile("bc14r13centers.txt"); + } + + public void testBc14r14centers() throws Exception { + processFile("bc14r14centers.txt"); + } + + public void testBc14r15centers() throws Exception { + processFile("bc14r15centers.txt"); + } + + public void testBc19r08centers() throws Exception { + processFile("bc19r08centers.txt"); + } + + public void testBc19r09centers() throws Exception { + processFile("bc19r09centers.txt"); + } + + public void testBc19r10centers() throws Exception { + processFile("bc19r10centers.txt"); + } + + public void testBc19r11centers() throws Exception { + processFile("bc19r11centers.txt"); + } + + public void testBc19r12centers() throws Exception { + processFile("bc19r12centers.txt"); + } + + public void testBc19r13centers() throws Exception { + processFile("bc19r13centers.txt"); + } + + public void testBc19r14centers() throws Exception { + processFile("bc19r14centers.txt"); + } + + public void testBc19r15centers() throws Exception { + processFile("bc19r15centers.txt"); + } + + private void processFile(String file) throws IOException { + InputStream fis = getClass().getResourceAsStream(file + ".gz"); + BufferedReader reader = new BufferedReader(new InputStreamReader(new GzipCompressorInputStream(fis), StandardCharsets.UTF_8)); + String line = reader.readLine(); + while (line != null) { + StringTokenizer tokenizer = new StringTokenizer(line, " "); + assertEquals(3, tokenizer.countTokens()); + String h3Address = tokenizer.nextToken(); + assertEquals(h3Address, true, H3.h3IsValid(h3Address)); + double lat = Double.parseDouble(tokenizer.nextToken()); + double lon = Double.parseDouble(tokenizer.nextToken()); + assertH3ToLatLng(h3Address, lat, lon); + assertGeoToH3(h3Address, lat, lon); + assertHexRing(h3Address); + line = reader.readLine(); + } + } + + private void assertH3ToLatLng(String h3Address, double lat, double lon) { + LatLng latLng = H3.h3ToLatLng(h3Address); + assertEquals(h3Address, lat, latLng.getLatDeg(), 1e-6); + assertEquals(h3Address, lon, latLng.getLonDeg(), 1e-6); + } + + private void assertGeoToH3(String h3Address, double lat, double lon) { + String computedH3Address = H3.geoToH3Address(lat, lon, H3Index.H3_get_resolution(H3.stringToH3(h3Address))); + assertEquals(h3Address, computedH3Address); + assertEquals(h3Address, computedH3Address); + } + + private void assertHexRing(String h3Address) { + String[] neighbors = H3.hexRing(h3Address); + long center = H3.stringToH3(h3Address); + for (String neighbor : neighbors) { + long l = H3.stringToH3(neighbor); + assertEquals(H3Index.H3_get_resolution(center), H3Index.H3_get_resolution(l)); + } + } +} diff --git a/libs/h3/src/test/java/org/elasticsearch/h3/GeoToH3Tests.java b/libs/h3/src/test/java/org/elasticsearch/h3/GeoToH3Tests.java new file mode 100644 index 0000000000000..09becde7f0f64 --- /dev/null +++ b/libs/h3/src/test/java/org/elasticsearch/h3/GeoToH3Tests.java @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.h3; + +import org.apache.lucene.geo.GeoTestUtil; +import org.elasticsearch.test.ESTestCase; + +public class GeoToH3Tests extends ESTestCase { + + public void testRandomPoints() { + for (int i = 0; i < 50; i++) { + // avoid points close to the poles + double lat = randomValueOtherThanMany(d -> d > 60 || d < -60, GeoTestUtil::nextLatitude); + // avoid points close to the dateline + double lon = randomValueOtherThanMany(d -> d > 150 || d < -150, GeoTestUtil::nextLongitude); + testPoint(lat, lon); + } + } + + private void testPoint(double lat, double lon) { + for (int i = 0; i < Constants.MAX_H3_RES; i++) { + String h3Address = H3.geoToH3Address(lat, lon, i); + CellBoundary cellBoundary = H3.h3ToGeoBoundary(h3Address); + double minLat = cellBoundary.getLatLon(0).getLatDeg(); + double maxLat = cellBoundary.getLatLon(0).getLatDeg(); + double minLon = cellBoundary.getLatLon(0).getLonDeg(); + double maxLon = cellBoundary.getLatLon(0).getLonDeg(); + for (int j = 0; j < cellBoundary.numPoints(); j++) { + minLat = Math.min(minLat, cellBoundary.getLatLon(j).getLatDeg()); + maxLat = Math.max(maxLat, cellBoundary.getLatLon(j).getLatDeg()); + minLon = Math.min(minLon, cellBoundary.getLatLon(j).getLonDeg()); + maxLon = Math.max(maxLon, cellBoundary.getLatLon(j).getLonDeg()); + } + assertTrue(minLat <= lat); + assertTrue(maxLat >= lat); + assertTrue(minLon <= lon); + assertTrue(maxLon >= lon); + } + } +} diff --git a/libs/h3/src/test/java/org/elasticsearch/h3/ParentChildNavigationTests.java b/libs/h3/src/test/java/org/elasticsearch/h3/ParentChildNavigationTests.java new file mode 100644 index 0000000000000..4e8ed3e4b6468 --- /dev/null +++ b/libs/h3/src/test/java/org/elasticsearch/h3/ParentChildNavigationTests.java @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.h3; + +import com.carrotsearch.randomizedtesting.generators.RandomPicks; + +import org.elasticsearch.test.ESTestCase; + +public class ParentChildNavigationTests extends ESTestCase { + + public void testParentChild() { + String[] h3Addresses = H3.getStringRes0Cells(); + String h3Address = RandomPicks.randomFrom(random(), h3Addresses); + String[] values = new String[H3.MAX_H3_RES]; + values[0] = h3Address; + for (int i = 1; i < H3.MAX_H3_RES; i++) { + h3Addresses = H3.h3ToChildren(h3Address); + h3Address = RandomPicks.randomFrom(random(), h3Addresses); + values[i] = h3Address; + } + h3Addresses = H3.h3ToChildren(h3Address); + h3Address = RandomPicks.randomFrom(random(), h3Addresses); + for (int i = H3.MAX_H3_RES - 1; i >= 0; i--) { + h3Address = H3.h3ToParent(h3Address); + assertEquals(values[i], h3Address); + } + } + + public void testHexRing() { + String[] h3Addresses = H3.getStringRes0Cells(); + String h3Address = RandomPicks.randomFrom(random(), h3Addresses); + for (int i = 1; i < H3.MAX_H3_RES; i++) { + h3Addresses = H3.h3ToChildren(h3Address); + assertHexRing(i, h3Address, h3Addresses); + h3Address = RandomPicks.randomFrom(random(), h3Addresses); + } + } + + private static final int[] HEX_RING_POSITIONS = new int[] { 2, 0, 1, 4, 3, 5 }; + private static final int[] PENT_RING_POSITIONS = new int[] { 0, 1, 3, 2, 4 }; + + private void assertHexRing(int res, String h3Address, String[] children) { + LatLng latLng = H3.h3ToLatLng(h3Address); + String centerChild = H3.geoToH3Address(latLng.getLatDeg(), latLng.getLonDeg(), res); + assertEquals(children[0], centerChild); + String[] ring = H3.hexRing(centerChild); + int[] positions = H3.isPentagon(centerChild) ? PENT_RING_POSITIONS : HEX_RING_POSITIONS; + for (int i = 1; i < children.length; i++) { + assertEquals(children[i], ring[positions[i - 1]]); + } + } +} diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/NOTICE.txt b/libs/h3/src/test/resources/org/elasticsearch/h3/NOTICE.txt new file mode 100644 index 0000000000000..50caeb4a70b61 --- /dev/null +++ b/libs/h3/src/test/resources/org/elasticsearch/h3/NOTICE.txt @@ -0,0 +1,4 @@ +The files under this directory come from the input test files from Uber's h3 repository +(https://github.com/uber/h3/tree/master/tests/inputfiles) and are made available here +under the same Apache 2 license. + diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r08cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r08cells.txt.gz new file mode 100644 index 0000000000000..e8904b41cf008 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r08cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r08centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r08centers.txt.gz new file mode 100644 index 0000000000000..9efeadc83938e Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r08centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r09cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r09cells.txt.gz new file mode 100644 index 0000000000000..dbb2e59344fb1 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r09cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r09centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r09centers.txt.gz new file mode 100644 index 0000000000000..9cbeae277c350 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r09centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r10cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r10cells.txt.gz new file mode 100644 index 0000000000000..f91ba38794763 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r10cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r10centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r10centers.txt.gz new file mode 100644 index 0000000000000..405a1e36507d0 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r10centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r11cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r11cells.txt.gz new file mode 100644 index 0000000000000..ebffe35712bad Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r11cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r11centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r11centers.txt.gz new file mode 100644 index 0000000000000..04c8ac2ff86e8 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r11centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r12cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r12cells.txt.gz new file mode 100644 index 0000000000000..8462076718599 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r12cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r12centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r12centers.txt.gz new file mode 100644 index 0000000000000..ae9cc56994471 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r12centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r13cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r13cells.txt.gz new file mode 100644 index 0000000000000..d8da966a35a7f Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r13cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r13centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r13centers.txt.gz new file mode 100644 index 0000000000000..e3e98279ac43c Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r13centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r14cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r14cells.txt.gz new file mode 100644 index 0000000000000..27be76a9ae3c3 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r14cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r14centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r14centers.txt.gz new file mode 100644 index 0000000000000..0695444fb4954 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r14centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r15cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r15cells.txt.gz new file mode 100644 index 0000000000000..73e1244722c99 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r15cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r15centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r15centers.txt.gz new file mode 100644 index 0000000000000..1ac7986927ae8 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc05r15centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r08cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r08cells.txt.gz new file mode 100644 index 0000000000000..5b0dc4330f0a2 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r08cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r08centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r08centers.txt.gz new file mode 100644 index 0000000000000..911f7e4c7176d Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r08centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r09cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r09cells.txt.gz new file mode 100644 index 0000000000000..f4a614bf13fc4 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r09cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r09centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r09centers.txt.gz new file mode 100644 index 0000000000000..28be0951ad07e Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r09centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r10cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r10cells.txt.gz new file mode 100644 index 0000000000000..95941dc11b07e Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r10cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r10centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r10centers.txt.gz new file mode 100644 index 0000000000000..da6b7d2987426 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r10centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r11cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r11cells.txt.gz new file mode 100644 index 0000000000000..e05bb28b59ebc Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r11cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r11centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r11centers.txt.gz new file mode 100644 index 0000000000000..636d4d149ceb4 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r11centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r12cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r12cells.txt.gz new file mode 100644 index 0000000000000..5fc28a11bbe76 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r12cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r12centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r12centers.txt.gz new file mode 100644 index 0000000000000..e0dd3f4395df7 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r12centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r13cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r13cells.txt.gz new file mode 100644 index 0000000000000..8c49ce4f37285 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r13cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r13centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r13centers.txt.gz new file mode 100644 index 0000000000000..2ea6219f01895 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r13centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r14cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r14cells.txt.gz new file mode 100644 index 0000000000000..caf54eb6d78bc Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r14cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r14centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r14centers.txt.gz new file mode 100644 index 0000000000000..2a96ded412aa3 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r14centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r15cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r15cells.txt.gz new file mode 100644 index 0000000000000..2a64b905e8754 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r15cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r15centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r15centers.txt.gz new file mode 100644 index 0000000000000..963b44b9528a5 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc14r15centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r08cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r08cells.txt.gz new file mode 100644 index 0000000000000..201c6d2cea76a Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r08cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r08centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r08centers.txt.gz new file mode 100644 index 0000000000000..f1011e078390b Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r08centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r09cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r09cells.txt.gz new file mode 100644 index 0000000000000..51bf7405fde56 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r09cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r09centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r09centers.txt.gz new file mode 100644 index 0000000000000..6e8dba126b54b Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r09centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r10cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r10cells.txt.gz new file mode 100644 index 0000000000000..1bb9cd27acbda Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r10cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r10centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r10centers.txt.gz new file mode 100644 index 0000000000000..c20c3029c65a4 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r10centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r11cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r11cells.txt.gz new file mode 100644 index 0000000000000..23d923d820436 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r11cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r11centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r11centers.txt.gz new file mode 100644 index 0000000000000..5a0a57a9c4034 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r11centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r12cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r12cells.txt.gz new file mode 100644 index 0000000000000..16631958efbf4 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r12cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r12centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r12centers.txt.gz new file mode 100644 index 0000000000000..77d2cb9d0b2c2 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r12centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r13cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r13cells.txt.gz new file mode 100644 index 0000000000000..74d5d33bebcf2 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r13cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r13centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r13centers.txt.gz new file mode 100644 index 0000000000000..961b5a4c3e1a7 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r13centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r14cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r14cells.txt.gz new file mode 100644 index 0000000000000..8c3c6ead1b2b2 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r14cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r14centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r14centers.txt.gz new file mode 100644 index 0000000000000..114ff52644288 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r14centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r15cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r15cells.txt.gz new file mode 100644 index 0000000000000..bb1394d19babc Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r15cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r15centers.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r15centers.txt.gz new file mode 100644 index 0000000000000..0b3a1ac52e038 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/bc19r15centers.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res00cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res00cells.txt.gz new file mode 100644 index 0000000000000..5b45234c7df4b Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res00cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res00ic.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res00ic.txt.gz new file mode 100644 index 0000000000000..498d8de3b7aca Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res00ic.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res01cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res01cells.txt.gz new file mode 100644 index 0000000000000..f6437ad72461e Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res01cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res01ic.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res01ic.txt.gz new file mode 100644 index 0000000000000..3d8f4569501d2 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res01ic.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res02cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res02cells.txt.gz new file mode 100644 index 0000000000000..0c63e3bbeed92 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res02cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res02ic.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res02ic.txt.gz new file mode 100644 index 0000000000000..9cfdb2b17eca0 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res02ic.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res03cells.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res03cells.txt.gz new file mode 100644 index 0000000000000..13cdb00539fb6 Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res03cells.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res03ic.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res03ic.txt.gz new file mode 100644 index 0000000000000..d72ae7a2904ac Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res03ic.txt.gz differ diff --git a/libs/h3/src/test/resources/org/elasticsearch/h3/res04ic.txt.gz b/libs/h3/src/test/resources/org/elasticsearch/h3/res04ic.txt.gz new file mode 100644 index 0000000000000..be6b73d1035ab Binary files /dev/null and b/libs/h3/src/test/resources/org/elasticsearch/h3/res04ic.txt.gz differ