浏览代码

Improve performance of H3.h3ToGeoBoundary (#117812) (#117824)

There are two clear code paths depending if a h3 bin belongs to even resolutions (class II) or 
uneven resolutions (class III). especializing the code paths for each type leads to an improvement in performance.
Ignacio Vera 10 月之前
父节点
当前提交
5fab2cf8f0
共有 2 个文件被更改,包括 142 次插入105 次删除
  1. 139 102
      libs/h3/src/main/java/org/elasticsearch/h3/FaceIJK.java
  2. 3 3
      libs/h3/src/main/java/org/elasticsearch/h3/H3.java

+ 139 - 102
libs/h3/src/main/java/org/elasticsearch/h3/FaceIJK.java

@@ -417,43 +417,64 @@ final class FaceIJK {
      * 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) {
+    public CellBoundary faceIjkPentToCellBoundary(int res) {
         // 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
-        int adjRes = res;
-        if (H3Index.isResolutionClassIII(res)) {
-            this.coord.downAp7r();
-            adjRes += 1;
-        }
+        final int adjRes = adjustRes(this.coord, res);
+
         // If we're returning the entire loop, we need one more iteration in case
         // of a distortion vertex on the last edge
-        final int additionalIteration = length == Constants.NUM_PENT_VERTS ? 1 : 0;
-        final boolean isResolutionClassIII = H3Index.isResolutionClassIII(res);
-        // convert each vertex to lat/lng
-        // adjust the face of each vertex as appropriate and introduce
-        // edge-crossing vertices as needed
+        if (H3Index.isResolutionClassIII(res)) {
+            return faceIjkPentToCellBoundaryClassIII(adjRes);
+        } else {
+            return faceIjkPentToCellBoundaryClassII(adjRes);
+        }
+    }
+
+    private CellBoundary faceIjkPentToCellBoundaryClassII(int adjRes) {
+        final LatLng[] points = new LatLng[Constants.NUM_PENT_VERTS];
+        final FaceIJK fijk = new FaceIJK(this.face, new CoordIJK(0, 0, 0));
+        for (int vert = 0; vert < Constants.NUM_PENT_VERTS; vert++) {
+            // 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.
+            fijk.coord.reset(
+                VERTEX_CLASSII[vert][0] + this.coord.i,
+                VERTEX_CLASSII[vert][1] + this.coord.j,
+                VERTEX_CLASSII[vert][2] + this.coord.k
+            );
+            fijk.coord.ijkNormalize();
+            fijk.face = this.face;
+
+            fijk.adjustPentVertOverage(adjRes);
+
+            points[vert] = fijk.coord.ijkToGeo(fijk.face, adjRes, true);
+        }
+        return new CellBoundary(points, Constants.NUM_PENT_VERTS);
+    }
+
+    private CellBoundary faceIjkPentToCellBoundaryClassIII(int adjRes) {
         final LatLng[] points = new LatLng[CellBoundary.MAX_CELL_BNDRY_VERTS];
         int numPoints = 0;
-        final CoordIJK scratch = new CoordIJK(0, 0, 0);
-        final FaceIJK fijk = new FaceIJK(this.face, scratch);
-        final int[][] coord = isResolutionClassIII ? VERTEX_CLASSIII : VERTEX_CLASSII;
+        final FaceIJK fijk = new FaceIJK(this.face, new CoordIJK(0, 0, 0));
         final CoordIJK lastCoord = new CoordIJK(0, 0, 0);
         int lastFace = this.face;
-        for (int vert = start; vert < start + length + additionalIteration; vert++) {
+        for (int vert = 0; vert < Constants.NUM_PENT_VERTS + 1; vert++) {
             final int v = vert % Constants.NUM_PENT_VERTS;
             // 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.
-            scratch.reset(coord[v][0], coord[v][1], coord[v][2]);
-            scratch.ijkAdd(this.coord.i, this.coord.j, this.coord.k);
-            scratch.ijkNormalize();
+            fijk.coord.reset(
+                VERTEX_CLASSIII[v][0] + this.coord.i,
+                VERTEX_CLASSIII[v][1] + this.coord.j,
+                VERTEX_CLASSIII[v][2] + this.coord.k
+            );
+            fijk.coord.ijkNormalize();
             fijk.face = this.face;
 
             fijk.adjustPentVertOverage(adjRes);
@@ -461,7 +482,7 @@ final class FaceIJK {
             // all Class III pentagon edges cross icosa edges
             // note that Class II pentagons have vertices on the edge,
             // not edge intersections
-            if (isResolutionClassIII && vert > start) {
+            if (vert > 0) {
                 // find hex2d of the two vertexes on the last face
                 final Vec2d orig2d0 = lastCoord.ijkToHex2d();
 
@@ -480,35 +501,17 @@ final class FaceIJK {
 
                 final Vec2d orig2d1 = lastCoord.ijkToHex2d();
 
-                // find the appropriate icosa face edge vertexes
-                final Vec2d edge0;
-                final Vec2d edge1;
-                switch (adjacentFaceDir[fijkOrient.face][fijk.face]) {
-                    case IJ -> {
-                        edge0 = maxDimByCIIVec2d[adjRes][0];
-                        edge1 = maxDimByCIIVec2d[adjRes][1];
-                    }
-                    case JK -> {
-                        edge0 = maxDimByCIIVec2d[adjRes][1];
-                        edge1 = maxDimByCIIVec2d[adjRes][2];
-                    }
-                    // case KI:
-                    default -> {
-                        assert (adjacentFaceDir[fijkOrient.face][fijk.face] == KI);
-                        edge0 = maxDimByCIIVec2d[adjRes][2];
-                        edge1 = maxDimByCIIVec2d[adjRes][0];
-                    }
-                }
-
                 // find the intersection and add the lat/lng point to the result
-                final Vec2d inter = Vec2d.v2dIntersect(orig2d0, orig2d1, edge0, edge1);
-                points[numPoints++] = inter.hex2dToGeo(fijkOrient.face, adjRes, true);
+                final Vec2d inter = findIntersectionPoint(orig2d0, orig2d1, adjRes, adjacentFaceDir[fijkOrient.face][fijk.face]);
+                if (inter != null) {
+                    points[numPoints++] = inter.hex2dToGeo(fijkOrient.face, adjRes, true);
+                }
             }
 
             // 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) {
+            if (vert < Constants.NUM_PENT_VERTS) {
                 points[numPoints++] = fijk.coord.ijkToGeo(fijk.face, adjRes, true);
             }
             lastFace = fijk.face;
@@ -522,10 +525,8 @@ final class FaceIJK {
      * 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(final int res, final int start, final int length) {
+    public CellBoundary faceIjkToCellBoundary(final int res) {
         // adjust the center point to be in an aperture 33r substrate grid
         // these should be composed for speed
         this.coord.downAp3();
@@ -533,32 +534,63 @@ final class FaceIJK {
 
         // if res is Class III we need to add a cw aperture 7 to get to
         // icosahedral Class II
-        int adjRes = res;
-        if (H3Index.isResolutionClassIII(res)) {
-            this.coord.downAp7r();
-            adjRes += 1;
-        }
+        final int adjRes = adjustRes(this.coord, res);
 
-        // If we're returning the entire loop, we need one more iteration in case
-        // of a distortion vertex on the last edge
-        final int additionalIteration = length == Constants.NUM_HEX_VERTS ? 1 : 0;
-        final boolean isResolutionClassIII = H3Index.isResolutionClassIII(res);
         // convert each vertex to lat/lng
         // adjust the face of each vertex as appropriate and introduce
         // edge-crossing vertices as needed
+        if (H3Index.isResolutionClassIII(res)) {
+            return faceIjkToCellBoundaryClassIII(adjRes);
+        } else {
+            return faceIjkToCellBoundaryClassII(adjRes);
+        }
+    }
+
+    private static int adjustRes(CoordIJK coord, int res) {
+        if (H3Index.isResolutionClassIII(res)) {
+            coord.downAp7r();
+            res += 1;
+        }
+        return res;
+    }
+
+    private CellBoundary faceIjkToCellBoundaryClassII(int adjRes) {
+        final LatLng[] points = new LatLng[Constants.NUM_HEX_VERTS];
+        final FaceIJK fijk = new FaceIJK(this.face, new CoordIJK(0, 0, 0));
+        for (int vert = 0; vert < Constants.NUM_HEX_VERTS; vert++) {
+            fijk.coord.reset(
+                VERTEX_CLASSII[vert][0] + this.coord.i,
+                VERTEX_CLASSII[vert][1] + this.coord.j,
+                VERTEX_CLASSII[vert][2] + this.coord.k
+            );
+            fijk.coord.ijkNormalize();
+            fijk.face = this.face;
+
+            fijk.adjustOverageClassII(adjRes, false, true);
+
+            // 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
+            points[vert] = fijk.coord.ijkToGeo(fijk.face, adjRes, true);
+        }
+        return new CellBoundary(points, Constants.NUM_HEX_VERTS);
+    }
+
+    private CellBoundary faceIjkToCellBoundaryClassIII(int adjRes) {
         final LatLng[] points = new LatLng[CellBoundary.MAX_CELL_BNDRY_VERTS];
         int numPoints = 0;
-        final CoordIJK scratch1 = new CoordIJK(0, 0, 0);
-        final FaceIJK fijk = new FaceIJK(this.face, scratch1);
-        final CoordIJK scratch2 = isResolutionClassIII ? new CoordIJK(0, 0, 0) : null;
-        final int[][] verts = isResolutionClassIII ? VERTEX_CLASSIII : VERTEX_CLASSII;
+        final FaceIJK fijk = new FaceIJK(this.face, new CoordIJK(0, 0, 0));
+        final CoordIJK scratch = new CoordIJK(0, 0, 0);
         int lastFace = -1;
         Overage lastOverage = Overage.NO_OVERAGE;
-        for (int vert = start; vert < start + length + additionalIteration; vert++) {
-            int v = vert % Constants.NUM_HEX_VERTS;
-            scratch1.reset(verts[v][0], verts[v][1], verts[v][2]);
-            scratch1.ijkAdd(this.coord.i, this.coord.j, this.coord.k);
-            scratch1.ijkNormalize();
+        for (int vert = 0; vert < Constants.NUM_HEX_VERTS + 1; vert++) {
+            final int v = vert % Constants.NUM_HEX_VERTS;
+            fijk.coord.reset(
+                VERTEX_CLASSIII[v][0] + this.coord.i,
+                VERTEX_CLASSIII[v][1] + this.coord.j,
+                VERTEX_CLASSIII[v][2] + this.coord.k
+            );
+            fijk.coord.ijkNormalize();
             fijk.face = this.face;
 
             final Overage overage = fijk.adjustOverageClassII(adjRes, false, true);
@@ -572,50 +604,20 @@ final class FaceIJK {
             projection. Note that Class II cell edges have vertices on the face
             edge, with no edge line intersections.
             */
-            if (isResolutionClassIII && vert > start && fijk.face != lastFace && lastOverage != Overage.FACE_EDGE) {
+            if (vert > 0 && fijk.face != lastFace && lastOverage != Overage.FACE_EDGE) {
                 // find hex2d of the two vertexes on original face
                 final int lastV = (v + 5) % Constants.NUM_HEX_VERTS;
                 // 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.
-                final int[] vertexLast = verts[lastV];
-                final int[] vertexV = verts[v];
-                scratch2.reset(vertexLast[0] + this.coord.i, vertexLast[1] + this.coord.j, vertexLast[2] + this.coord.k);
-                scratch2.ijkNormalize();
-                final Vec2d orig2d0 = scratch2.ijkToHex2d();
-                scratch2.reset(vertexV[0] + this.coord.i, vertexV[1] + this.coord.j, vertexV[2] + this.coord.k);
-                scratch2.ijkNormalize();
-                final Vec2d orig2d1 = scratch2.ijkToHex2d();
+                final Vec2d orig2d0 = orig(scratch, VERTEX_CLASSIII[lastV]);
+                final Vec2d orig2d1 = orig(scratch, VERTEX_CLASSIII[v]);
 
                 // find the appropriate icosa face edge vertexes
                 final int face2 = ((lastFace == this.face) ? fijk.face : lastFace);
-                final Vec2d edge0;
-                final Vec2d edge1;
-                switch (adjacentFaceDir[this.face][face2]) {
-                    case IJ -> {
-                        edge0 = maxDimByCIIVec2d[adjRes][0];
-                        edge1 = maxDimByCIIVec2d[adjRes][1];
-                    }
-                    case JK -> {
-                        edge0 = maxDimByCIIVec2d[adjRes][1];
-                        edge1 = maxDimByCIIVec2d[adjRes][2];
-                    }
-                    // case KI:
-                    default -> {
-                        assert (adjacentFaceDir[this.face][face2] == KI);
-                        edge0 = maxDimByCIIVec2d[adjRes][2];
-                        edge1 = maxDimByCIIVec2d[adjRes][0];
-                    }
-                }
                 // find the intersection and add the lat/lng point to the result
-                final 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.
-                */
-                final boolean isIntersectionAtVertex = orig2d0.numericallyIdentical(inter) || orig2d1.numericallyIdentical(inter);
-                if (isIntersectionAtVertex == false) {
+                final Vec2d inter = findIntersectionPoint(orig2d0, orig2d1, adjRes, adjacentFaceDir[this.face][face2]);
+                if (inter != null) {
                     points[numPoints++] = inter.hex2dToGeo(this.face, adjRes, true);
                 }
             }
@@ -623,7 +625,7 @@ final class FaceIJK {
             // 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) {
+            if (vert < Constants.NUM_HEX_VERTS) {
                 points[numPoints++] = fijk.coord.ijkToGeo(fijk.face, adjRes, true);
             }
             lastFace = fijk.face;
@@ -632,6 +634,42 @@ final class FaceIJK {
         return new CellBoundary(points, numPoints);
     }
 
+    private Vec2d orig(CoordIJK scratch, int[] vertexLast) {
+        scratch.reset(vertexLast[0] + this.coord.i, vertexLast[1] + this.coord.j, vertexLast[2] + this.coord.k);
+        scratch.ijkNormalize();
+        return scratch.ijkToHex2d();
+    }
+
+    private Vec2d findIntersectionPoint(Vec2d orig2d0, Vec2d orig2d1, int adjRes, int faceDir) {
+        // find the appropriate icosa face edge vertexes
+        final Vec2d edge0;
+        final Vec2d edge1;
+        switch (faceDir) {
+            case IJ -> {
+                edge0 = maxDimByCIIVec2d[adjRes][0];
+                edge1 = maxDimByCIIVec2d[adjRes][1];
+            }
+            case JK -> {
+                edge0 = maxDimByCIIVec2d[adjRes][1];
+                edge1 = maxDimByCIIVec2d[adjRes][2];
+            }
+            // case KI:
+            default -> {
+                assert (faceDir == KI);
+                edge0 = maxDimByCIIVec2d[adjRes][2];
+                edge1 = maxDimByCIIVec2d[adjRes][0];
+            }
+        }
+        // find the intersection and add the lat/lng point to the result
+        final 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.
+        */
+        return orig2d0.numericallyIdentical(inter) || orig2d1.numericallyIdentical(inter) ? null : inter;
+    }
+
     /**
      * compute the corresponding H3Index.
      * @param res The cell resolution.
@@ -651,7 +689,6 @@ final class FaceIJK {
                 // out of range input
                 throw new IllegalArgumentException(" out of range input");
             }
-
             return H3Index.H3_set_base_cell(h, BaseCells.getBaseCell(face, coord));
         }
 

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

@@ -174,11 +174,11 @@ public final class H3 {
      * Find the cell {@link CellBoundary} coordinates for the cell
      */
     public static CellBoundary h3ToGeoBoundary(long h3) {
-        FaceIJK fijk = H3Index.h3ToFaceIjk(h3);
+        final FaceIJK fijk = H3Index.h3ToFaceIjk(h3);
         if (H3Index.H3_is_pentagon(h3)) {
-            return fijk.faceIjkPentToCellBoundary(H3Index.H3_get_resolution(h3), 0, Constants.NUM_PENT_VERTS);
+            return fijk.faceIjkPentToCellBoundary(H3Index.H3_get_resolution(h3));
         } else {
-            return fijk.faceIjkToCellBoundary(H3Index.H3_get_resolution(h3), 0, Constants.NUM_HEX_VERTS);
+            return fijk.faceIjkToCellBoundary(H3Index.H3_get_resolution(h3));
         }
     }