diff --git a/build.gradle b/build.gradle index 28d55e76e6..f08eacce92 100644 --- a/build.gradle +++ b/build.gradle @@ -136,6 +136,8 @@ publishing { // Dependencies //****************************************************************************/ dependencies { + implementation "org.opensearch.plugin:geo:${opensearch_version}" + api project(":libs:h3") yamlRestTestRuntimeOnly "org.apache.logging.log4j:log4j-core:${versions.log4j}" testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}" testImplementation 'org.json:json:20211205' diff --git a/libs/build.gradle b/libs/build.gradle new file mode 100644 index 0000000000..32bdf69d2b --- /dev/null +++ b/libs/build.gradle @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +subprojects { + apply plugin: 'opensearch.build' +} diff --git a/libs/h3/LICENSE.txt b/libs/h3/LICENSE.txt new file mode 100644 index 0000000000..3ab280ebc8 --- /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 0000000000..5201d8e6e2 --- /dev/null +++ b/libs/h3/NOTICE.txt @@ -0,0 +1,25 @@ +OpenSearch (https://opensearch.org/) +Copyright OpenSearch Contributors + +-- +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 0000000000..597aa0e53a --- /dev/null +++ b/libs/h3/build.gradle @@ -0,0 +1,74 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +/* + * 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: 'opensearch.build' +apply plugin: 'opensearch.publish' + +tasks.named('forbiddenApisMain').configure { + replaceSignatureFiles 'jdk-signatures' +} + +repositories { + mavenLocal() + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } + mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } +} + +dependencies { + api "org.apache.logging.log4j:log4j-api:${versions.log4j}" + api "org.apache.logging.log4j:log4j-core:${versions.log4j}" + testImplementation "org.opensearch.test:framework:${opensearch_version}" + testImplementation "org.apache.commons:commons-compress:1.21" +} +licenseFile = "LICENSE.txt" +noticeFile = "NOTICE.txt" + +project.dependencyLicenses.enabled = false +project.thirdPartyAudit.enabled = false +project.loggerUsageCheck.enabled = false +project.forbiddenApis.ignoreFailures = true + +publishing { + publications { + pluginZip(MavenPublication) { publication -> + pom { + name = "opensearch-geospatial-h3" + description = 'OpenSearch Geospatial H3 library' + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + name = "OpenSearch" + url = "https://github.com/opensearch-project/geospatial/libs/h3" + } + } + } + } + } +} + + diff --git a/libs/h3/src/main/java/org/opensearch/geospatial/h3/BaseCells.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/BaseCells.java new file mode 100644 index 0000000000..5e5bd93c4b --- /dev/null +++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/BaseCells.java @@ -0,0 +1,656 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +/* + * 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.opensearch.geospatial.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/opensearch/geospatial/h3/CellBoundary.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/CellBoundary.java
new file mode 100644
index 0000000000..72dbd83260
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/CellBoundary.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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/opensearch/geospatial/h3/Constants.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/Constants.java
new file mode 100644
index 0000000000..6164eec8c5
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/Constants.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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;
+ /**
+ * H3 version 1 has 16 resolutions, numbered 0 through 15
+ * min H3 resolution
+ */
+ public static int MIN_H3_RES = 0;
+ /**
+ * max H3 resolution;
+ */
+ 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/opensearch/geospatial/h3/CoordIJK.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/CoordIJK.java
new file mode 100644
index 0000000000..afe4dd1ba9
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/CoordIJK.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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/opensearch/geospatial/h3/FaceIJK.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/FaceIJK.java
new file mode 100644
index 0000000000..7a37c23254
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/FaceIJK.java
@@ -0,0 +1,817 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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/opensearch/geospatial/h3/H3.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/H3.java
new file mode 100644
index 0000000000..ad55d00a15
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/H3.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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 MIN_H3_RES = Constants.MIN_H3_RES;
+ 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 < Constants.MIN_H3_RES || 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/opensearch/geospatial/h3/H3Index.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/H3Index.java
new file mode 100644
index 0000000000..f12fcac199
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/H3Index.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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/opensearch/geospatial/h3/HexRing.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/HexRing.java
new file mode 100644
index 0000000000..9f95f1a66c
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/HexRing.java
@@ -0,0 +1,760 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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/opensearch/geospatial/h3/Iterator.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/Iterator.java new file mode 100644 index 0000000000..ad21842d19 --- /dev/null +++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/Iterator.java @@ -0,0 +1,310 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +/* + * 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.opensearch.geospatial.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/opensearch/geospatial/h3/LatLng.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/LatLng.java
new file mode 100644
index 0000000000..427e1e3d8f
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/LatLng.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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/opensearch/geospatial/h3/Vec2d.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/Vec2d.java
new file mode 100644
index 0000000000..aefd3ce8ca
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/Vec2d.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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/opensearch/geospatial/h3/Vec3d.java b/libs/h3/src/main/java/org/opensearch/geospatial/h3/Vec3d.java
new file mode 100644
index 0000000000..f4b75ef8f9
--- /dev/null
+++ b/libs/h3/src/main/java/org/opensearch/geospatial/h3/Vec3d.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.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/opensearch/geospatial/h3/CellBoundaryTests.java b/libs/h3/src/test/java/org/opensearch/geospatial/h3/CellBoundaryTests.java
new file mode 100644
index 0000000000..82c2263528
--- /dev/null
+++ b/libs/h3/src/test/java/org/opensearch/geospatial/h3/CellBoundaryTests.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/*
+ * 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.opensearch.geospatial.h3;
+
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import org.opensearch.test.OpenSearchTestCase;
+
+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 OpenSearchTestCase {
+
+ 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();
+ Listlong
representation of an index to {@link GeoPoint} representation.
+ * @param h3CellID H3 Cell Id
+ * @throws IllegalArgumentException if invalid h3CellID is provided
+ */
+ public static GeoPoint h3ToGeoPoint(long h3CellID) {
+ if (h3IsValid(h3CellID) == false) {
+ throw new IllegalArgumentException(String.format(Locale.getDefault(), "Invalid H3 Cell address: %d", h3CellID));
+ }
+ final var position = h3ToLatLng(h3CellID);
+ return new GeoPoint(position.getLatDeg(), position.getLonDeg());
+ }
+
+ /**
+ * Converts from {@link String} representation of an index to {@link GeoPoint} representation.
+ * @param h3CellID H3 Cell Id
+ * @throws IllegalArgumentException if invalid h3CellID is provided
+ */
+ public static GeoPoint h3ToGeoPoint(@NonNull String h3CellID) {
+ return h3ToGeoPoint(stringToH3(h3CellID));
+ }
+
+ /**
+ * Encodes longitude/latitude into H3 Cell Address for given precision
+ *
+ * @param latitude Latitude in degrees.
+ * @param longitude Longitude in degrees.
+ * @param precision Precision, 0 <= res <= 15
+ * @return The H3 index.
+ * @throws IllegalArgumentException latitude, longitude, or precision are out of range.
+ */
+ public static long longEncode(double longitude, double latitude, int precision) {
+ return geoToH3(latitude, longitude, precision);
+ }
+}
diff --git a/src/main/java/org/opensearch/geospatial/search/aggregations/bucket/geogrid/ParsedGeoHexGrid.java b/src/main/java/org/opensearch/geospatial/search/aggregations/bucket/geogrid/ParsedGeoHexGrid.java
new file mode 100644
index 0000000000..7e0dcda2d0
--- /dev/null
+++ b/src/main/java/org/opensearch/geospatial/search/aggregations/bucket/geogrid/ParsedGeoHexGrid.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.opensearch.geospatial.search.aggregations.bucket.geogrid;
+
+import java.io.IOException;
+
+import lombok.NoArgsConstructor;
+
+import org.opensearch.common.xcontent.ObjectParser;
+import org.opensearch.common.xcontent.XContentParser;
+import org.opensearch.geo.search.aggregations.bucket.geogrid.ParsedGeoGrid;
+
+@NoArgsConstructor
+public class ParsedGeoHexGrid extends ParsedGeoGrid {
+ private static final ObjectParser