Browse Source

Add new H3 api method #h3ToNoChildrenIntersecting (#91673)

Given an H3 index, this method returns all the h3 index in the next resolution that intersects the provided h3 
index but they are not children from it.
Ignacio Vera 2 years ago
parent
commit
3286bae8c6

+ 5 - 0
docs/changelog/91673.yaml

@@ -0,0 +1,5 @@
+pr: 91673
+summary: "Add new H3 api method #h3ToNoChildrenIntersecting"
+area: Geo
+type: enhancement
+issues: []

+ 28 - 0
libs/h3/src/main/java/org/elasticsearch/h3/H3.java

@@ -248,6 +248,34 @@ public final class H3 {
         return h3ToStringList(h3ToChildren(stringToH3(h3Address)));
     }
 
+    private static final int[] PEN_INTERSECTING_CHILDREN_DIRECTIONS = new int[] { 3, 1, 6, 4, 2 };
+    private static final int[] HEX_INTERSECTING_CHILDREN_DIRECTIONS = new int[] { 3, 6, 2, 5, 1, 4 };
+
+    /**
+     * Returns the h3 bins on the level below which are not children of the given H3 index but
+     * intersects with it.
+     */
+    public static long[] h3ToNoChildrenIntersecting(long h3) {
+        final long[] children = new long[cellToChildrenSize(h3) - 1];
+        final Iterator.IterCellsChildren it = Iterator.iterInitParent(h3, H3Index.H3_get_resolution(h3) + 1);
+        final int[] directions = H3.isPentagon(it.h) ? PEN_INTERSECTING_CHILDREN_DIRECTIONS : HEX_INTERSECTING_CHILDREN_DIRECTIONS;
+        int pos = 0;
+        Iterator.iterStepChild(it);
+        while (it.h != Iterator.H3_NULL) {
+            children[pos] = HexRing.h3NeighborInDirection(it.h, directions[pos++]);
+            Iterator.iterStepChild(it);
+        }
+        return children;
+    }
+
+    /**
+     * Returns the h3 addresses on the level below which are not children of the given H3 address but
+     * intersects with it.
+     */
+    public static String[] h3ToNoChildrenIntersecting(String h3Address) {
+        return h3ToStringList(h3ToNoChildrenIntersecting(stringToH3(h3Address)));
+    }
+
     /**
      * Returns the neighbor indexes.
      *

+ 1 - 1
libs/h3/src/main/java/org/elasticsearch/h3/HexRing.java

@@ -700,7 +700,7 @@ final class HexRing {
      * @param dir Direction to move in
      * @return H3Index of the specified neighbor or -1 if there is no more neighbor
      */
-    private static long h3NeighborInDirection(long origin, int dir) {
+    static long h3NeighborInDirection(long origin, int dir) {
         long current = origin;
 
         int newRotations = 0;

+ 43 - 2
libs/h3/src/test/java/org/elasticsearch/h3/ParentChildNavigationTests.java

@@ -20,8 +20,15 @@ package org.elasticsearch.h3;
 
 import com.carrotsearch.randomizedtesting.generators.RandomPicks;
 
+import org.apache.lucene.spatial3d.geom.GeoPoint;
+import org.apache.lucene.spatial3d.geom.GeoPolygon;
+import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
+import org.apache.lucene.spatial3d.geom.PlanetModel;
 import org.elasticsearch.test.ESTestCase;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class ParentChildNavigationTests extends ESTestCase {
 
     public void testParentChild() {
@@ -44,11 +51,11 @@ public class ParentChildNavigationTests extends ESTestCase {
 
     public void testHexRing() {
         String[] h3Addresses = H3.getStringRes0Cells();
-        String h3Address = RandomPicks.randomFrom(random(), h3Addresses);
         for (int i = 1; i < H3.MAX_H3_RES; i++) {
+            String h3Address = RandomPicks.randomFrom(random(), h3Addresses);
+            assertEquals(i - 1, H3.getResolution(h3Address));
             h3Addresses = H3.h3ToChildren(h3Address);
             assertHexRing(i, h3Address, h3Addresses);
-            h3Address = RandomPicks.randomFrom(random(), h3Addresses);
         }
     }
 
@@ -65,4 +72,38 @@ public class ParentChildNavigationTests extends ESTestCase {
             assertEquals(children[i], ring[positions[i - 1]]);
         }
     }
+
+    public void testNoChildrenIntersecting() {
+        String[] h3Addresses = H3.getStringRes0Cells();
+        String h3Address = RandomPicks.randomFrom(random(), h3Addresses);
+        for (int i = 1; i <= H3.MAX_H3_RES; i++) {
+            h3Addresses = H3.h3ToChildren(h3Address);
+            assertIntersectingChildren(h3Address, h3Addresses);
+            h3Address = RandomPicks.randomFrom(random(), h3Addresses);
+        }
+    }
+
+    private void assertIntersectingChildren(String h3Address, String[] children) {
+        String[] intersectingNotChildren = H3.h3ToNoChildrenIntersecting(h3Address);
+        for (String noChild : intersectingNotChildren) {
+            GeoPolygon p = getGeoPolygon(noChild);
+            int intersections = 0;
+            for (String o : children) {
+                if (p.intersects(getGeoPolygon(o))) {
+                    intersections++;
+                }
+            }
+            assertEquals(2, intersections);
+        }
+    }
+
+    private GeoPolygon getGeoPolygon(String h3Address) {
+        CellBoundary cellBoundary = H3.h3ToGeoBoundary(h3Address);
+        List<GeoPoint> points = new ArrayList<>(cellBoundary.numPoints());
+        for (int i = 0; i < cellBoundary.numPoints(); i++) {
+            LatLng latLng = cellBoundary.getLatLon(i);
+            points.add(new GeoPoint(PlanetModel.SPHERE, latLng.getLatRad(), latLng.getLonRad()));
+        }
+        return GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points);
+    }
 }