
org.elasticsearch.h3.FaceIJK Maven / Gradle / Ivy
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* This project is based on a modification of https://github.com/uber/h3 which is licensed under the Apache 2.0 License.
*
* Copyright 2016-2021 Uber Technologies, Inc.
*/
package org.elasticsearch.h3;
/**
* Mutable face number and ijk coordinates on that face-centered coordinate system.
*
* References the Vec2d cartesian coordinate systems hex2d: local face-centered
* coordinate system scaled a specific H3 grid resolution unit length and
* with x-axes aligned with the local i-axes
*/
final class FaceIJK {
/** enum representing overage type */
enum Overage {
/**
* Digit representing overage type
*/
NO_OVERAGE,
/**
* On face edge (only occurs on substrate grids)
*/
FACE_EDGE,
/**
* Overage on new face interior
*/
NEW_FACE
}
// indexes for faceNeighbors table
/**
* IJ quadrant faceNeighbors table direction
*/
private static final int IJ = 1;
/**
* KI quadrant faceNeighbors table direction
*/
private static final int KI = 2;
/**
* JK quadrant faceNeighbors table direction
*/
private static final int JK = 3;
/**
* overage distance table
*/
private static final int[] maxDimByCIIres = {
2, // res 0
-1, // res 1
14, // res 2
-1, // res 3
98, // res 4
-1, // res 5
686, // res 6
-1, // res 7
4802, // res 8
-1, // res 9
33614, // res 10
-1, // res 11
235298, // res 12
-1, // res 13
1647086, // res 14
-1, // res 15
11529602 // res 16
};
private static final Vec2d[][] maxDimByCIIVec2d = new Vec2d[maxDimByCIIres.length][3];
static {
for (int i = 0; i < maxDimByCIIres.length; i++) {
maxDimByCIIVec2d[i][0] = new Vec2d(3.0 * maxDimByCIIres[i], 0.0);
maxDimByCIIVec2d[i][1] = new Vec2d(-1.5 * maxDimByCIIres[i], 3.0 * Constants.M_SQRT3_2 * maxDimByCIIres[i]);
maxDimByCIIVec2d[i][2] = new Vec2d(-1.5 * maxDimByCIIres[i], -3.0 * Constants.M_SQRT3_2 * maxDimByCIIres[i]);
}
}
/**
* 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
} };
// 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
private static final int[][] VERTEX_CLASSIII = new int[][] {
{ 5, 4, 0 }, // 0
{ 1, 5, 0 }, // 1
{ 0, 5, 4 }, // 2
{ 0, 1, 5 }, // 3
{ 4, 0, 5 }, // 4
{ 5, 0, 1 } // 5
};
// 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
private static final int[][] VERTEX_CLASSII = new int[][] {
{ 2, 1, 0 }, // 0
{ 1, 2, 0 }, // 1
{ 0, 2, 1 }, // 2
{ 0, 1, 2 }, // 3
{ 1, 0, 2 }, // 4
{ 2, 0, 1 } // 5
};
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) {
return coord.ijkToGeo(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) {
// 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;
}
// 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
final CellBoundary boundary = new CellBoundary();
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 CoordIJK lastCoord = new CoordIJK(0, 0, 0);
int lastFace = this.face;
for (int vert = start; vert < start + length + additionalIteration; 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.face = this.face;
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 (isResolutionClassIII && vert > start) {
// find hex2d of the two vertexes on the last face
final Vec2d orig2d0 = lastCoord.ijkToHex2d();
final int currentToLastDir = adjacentFaceDir[fijk.face][lastFace];
final FaceOrientIJK fijkOrient = faceNeighbors[fijk.face][currentToLastDir];
lastCoord.reset(fijk.coord.i, fijk.coord.j, fijk.coord.k);
// rotate and translate for adjacent face
for (int i = 0; i < fijkOrient.ccwRot60; i++) {
lastCoord.ijkRotate60ccw();
}
final int unitScale = unitScaleByCIIres[adjRes] * 3;
lastCoord.ijkAdd(
Math.multiplyExact(fijkOrient.translateI, unitScale),
Math.multiplyExact(fijkOrient.translateJ, unitScale),
Math.multiplyExact(fijkOrient.translateK, unitScale)
);
lastCoord.ijkNormalize();
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);
final LatLng point = inter.hex2dToGeo(fijkOrient.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) {
final LatLng point = fijk.coord.ijkToGeo(fijk.face, adjRes, true);
boundary.add(point);
}
lastFace = fijk.face;
lastCoord.reset(fijk.coord.i, fijk.coord.j, fijk.coord.k);
}
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(final int res, final int start, final int length) {
// 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;
}
// 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
final CellBoundary boundary = new CellBoundary();
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;
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();
fijk.face = this.face;
final Overage overage = fijk.adjustOverageClassII(adjRes, false, 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 (isResolutionClassIII && vert > start && 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(
Math.addExact(vertexLast[0], this.coord.i),
Math.addExact(vertexLast[1], this.coord.j),
Math.addExact(vertexLast[2], this.coord.k)
);
scratch2.ijkNormalize();
final Vec2d orig2d0 = scratch2.ijkToHex2d();
scratch2.reset(
Math.addExact(vertexV[0], this.coord.i),
Math.addExact(vertexV[1], this.coord.j),
Math.addExact(vertexV[2], this.coord.k)
);
scratch2.ijkNormalize();
final Vec2d orig2d1 = scratch2.ijkToHex2d();
// 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 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) {
final LatLng point = fijk.coord.ijkToGeo(fijk.face, adjRes, true);
boundary.add(point);
}
lastFace = fijk.face;
lastOverage = overage;
}
return boundary;
}
/**
* compute the corresponding H3Index.
* @param res The cell resolution.
* @param face The face.
* @param coord The CoordIJK.
* @return The encoded H3Index
*/
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);
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(face, coord));
}
// 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
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();
scratch.reset(coord.i, coord.j, coord.k);
scratch.downAp7();
} else {
// rotate cw
coord.upAp7r();
scratch.reset(coord.i, coord.j, coord.k);
scratch.downAp7r();
}
scratch.reset(Math.subtractExact(lastI, scratch.i), Math.subtractExact(lastJ, scratch.j), Math.subtractExact(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 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
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
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()) {
// 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;
}
/**
* 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 void adjustPentVertOverage(int res) {
Overage overage;
do {
overage = adjustOverageClassII(res, false, true);
} while (overage == Overage.NEW_FACE);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy