Skip to content

Commit

Permalink
Reduce number of object allocations in H3#geoToH3 and speed up comput…
Browse files Browse the repository at this point in the history
…ations (#91492)
  • Loading branch information
iverase committed Nov 14, 2022
1 parent a363902 commit 5befc4f
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 212 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/91492.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 91492
summary: Reduce number of object allocations in H3#geoToH3 and speed up computations
area: Geo
type: enhancement
issues: []
12 changes: 6 additions & 6 deletions libs/h3/src/main/java/org/elasticsearch/h3/BaseCells.java
Original file line number Diff line number Diff line change
Expand Up @@ -613,28 +613,28 @@ public static FaceIJK getBaseFaceIJK(int baseCell) {
return new FaceIJK(cellData.homeFace, new CoordIJK(cellData.homeI, cellData.homeJ, cellData.homeK));
}

/** Find base cell given FaceIJK.
/** Find base cell given a face and a CoordIJK.
*
* 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;
public static int getBaseCell(int face, CoordIJK coord) {
return faceIjkBaseCells[face][coord.i][coord.j][coord.k].baseCell;
}

/** Find base cell given FaceIJK.
/** Find base cell given a face and a CoordIJK.
*
* 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;
public static int getBaseCellCCWrot60(int face, CoordIJK coord) {
return faceIjkBaseCells[face][coord.i][coord.j][coord.k].ccwRot60;
}

/** Return whether or not the tested face is a cw offset face.
Expand Down
137 changes: 52 additions & 85 deletions libs/h3/src/main/java/org/elasticsearch/h3/CoordIJK.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,20 @@ public int digit() {
}

/**
* Find the center point in 2D cartesian coordinates of a hex.
* Reset the value of the IJK coordinates to the provided ones.
*
* @param i the i coordinate
* @param j the j coordinate
* @param k the k coordinate
*/
void reset(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;
Expand Down Expand Up @@ -128,41 +140,13 @@ public void ijkSub(int i, int j, int k) {

/**
* Normalizes ijk coordinates by setting the ijk coordinates
* to the smallest possible values.
* to the smallest possible positive 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;
}
final int min = Math.min(i, Math.min(j, k));
i -= min;
j -= min;
k -= min;
}

/**
Expand Down Expand Up @@ -321,26 +305,11 @@ public void upAp7() {
* 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;
}
// should be call on a normalized object
if (Math.min(i, Math.min(j, k)) < 0 || Math.max(i, Math.max(j, k)) > 1) {
return Direction.INVALID_DIGIT.digit();
}
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]);
return i << 2 | j << 1 | k;
}

/**
Expand All @@ -349,22 +318,21 @@ private boolean ijkMatches(int[] c) {
* @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;
}
return switch (digit) {
case 1 -> // K_AXES_DIGIT
Direction.JK_AXES_DIGIT.digit();
case 3 -> // JK_AXES_DIGIT:
Direction.J_AXES_DIGIT.digit();
case 2 -> // J_AXES_DIGIT:
Direction.IJ_AXES_DIGIT.digit();
case 6 -> // IJ_AXES_DIGIT
Direction.I_AXES_DIGIT.digit();
case 4 -> // I_AXES_DIGIT
Direction.IK_AXES_DIGIT.digit();
case 5 -> // IK_AXES_DIGIT
Direction.K_AXES_DIGIT.digit();
default -> digit;
};
}

/**
Expand All @@ -373,22 +341,21 @@ public static int rotate60cw(int digit) {
* @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;
}
return switch (digit) {
case 1 -> // K_AXES_DIGIT
Direction.IK_AXES_DIGIT.digit();
case 5 -> // IK_AXES_DIGIT
Direction.I_AXES_DIGIT.digit();
case 4 -> // I_AXES_DIGIT
Direction.IJ_AXES_DIGIT.digit();
case 6 -> // IJ_AXES_DIGIT
Direction.J_AXES_DIGIT.digit();
case 2 -> // J_AXES_DIGIT:
Direction.JK_AXES_DIGIT.digit();
case 3 -> // JK_AXES_DIGIT:
Direction.K_AXES_DIGIT.digit();
default -> digit;
};
}

}
44 changes: 22 additions & 22 deletions libs/h3/src/main/java/org/elasticsearch/h3/FaceIJK.java
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,11 @@ public CellBoundary faceIjkToCellBoundary(int res, int start, int length) {
/**
* compute the corresponding H3Index.
* @param res The cell resolution.
* @return The encoded H3Index (or H3_NULL on failure).
* @param face The face.
* @param coord The CoordIJK.
* @return The encoded H3Index
*/
public long faceIjkToH3(int res) {
static long faceIjkToH3(int res, int face, CoordIJK coord) {
// initialize the index
long h = H3Index.H3_INIT;
h = H3Index.H3_set_mode(h, Constants.H3_CELL_MODE);
Expand All @@ -607,53 +609,51 @@ public long faceIjkToH3(int res) {
throw new IllegalArgumentException(" out of range input");
}

return H3Index.H3_set_base_cell(h, BaseCells.getBaseCell(this));
return H3Index.H3_set_base_cell(h, BaseCells.getBaseCell(face, coord));
}

// we need to find the correct base cell FaceIJK for this H3 index;
// we need to find the correct base cell CoordIJK 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)) {
final CoordIJK scratch = new CoordIJK(0, 0, 0);
for (int r = res; r > 0; r--) {
final int lastI = coord.i;
final int lastJ = coord.j;
final int lastK = coord.k;
if (H3Index.isResolutionClassIII(r)) {
// rotate ccw
coord.upAp7();
lastCenter = new CoordIJK(coord.i, coord.j, coord.k);
lastCenter.downAp7();
scratch.reset(coord.i, coord.j, coord.k);
scratch.downAp7();
} else {
// rotate cw
coord.upAp7r();
lastCenter = new CoordIJK(coord.i, coord.j, coord.k);
lastCenter.downAp7r();
scratch.reset(coord.i, coord.j, coord.k);
scratch.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());
scratch.reset(lastI - scratch.i, lastJ - scratch.j, lastK - scratch.k);
scratch.ijkNormalize();
h = H3Index.H3_set_index_digit(h, r, scratch.unitIjkToDigit());
}

// we should now hold the IJK of the base cell in the
// coordinate system of the current face

// coordinate system of the given 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);
final int baseCell = BaseCells.getBaseCell(face, coord);
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);
final int numRots = BaseCells.getBaseCellCCWrot60(face, coord);
if (BaseCells.isBaseCellPentagon(baseCell)) {
// force rotation out of missing k-axes sub-sequence
if (H3Index.h3LeadingNonZeroDigit(h) == CoordIJK.Direction.K_AXES_DIGIT.digit()) {
Expand Down
2 changes: 1 addition & 1 deletion libs/h3/src/main/java/org/elasticsearch/h3/H3.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public static CellBoundary h3ToGeoBoundary(String h3Address) {
*/
public static long geoToH3(double lat, double lng, int res) {
checkResolution(res);
return new LatLng(toRadians(lat), toRadians(lng)).geoToFaceIJK(res).faceIjkToH3(res);
return Vec3d.geoToH3(res, toRadians(lat), toRadians(lng));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion libs/h3/src/main/java/org/elasticsearch/h3/H3Index.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public static long H3_set_index_digit(long h3, int res, long digit) {
* a Class II grid.
*/
public static boolean isResolutionClassIII(int res) {
return res % 2 != 0;
return (res & 1) == 1;
}

/**
Expand Down
61 changes: 5 additions & 56 deletions libs/h3/src/main/java/org/elasticsearch/h3/LatLng.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,68 +54,17 @@ 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.
* @param lat The latitude in radians.
* @param lon The longitude in radians.
* @return The azimuth in radians.
*/
private double geoAzimuthRads(LatLng p) {
double geoAzimuthRads(double lat, double lon) {
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)
Math.cos(lat) * Math.sin(lon - this.lon),
Math.cos(this.lat) * Math.sin(lat) - Math.sin(this.lat) * Math.cos(lat) * Math.cos(lon - this.lon)
);
}
}

0 comments on commit 5befc4f

Please sign in to comment.