src/h3lib/lib/vertex.c (216 lines of code) (raw):
/*
* Copyright 2020-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.
*/
/** @file vertex.h
* @brief Functions for working with cell vertexes.
*/
#include "vertex.h"
#include <assert.h>
#include <stdbool.h>
#include "algos.h"
#include "baseCells.h"
#include "faceijk.h"
#include "h3Assert.h"
#include "h3Index.h"
#include "latLng.h"
#define DIRECTION_INDEX_OFFSET 2
/** @brief Table of direction-to-face mapping for each pentagon
*
* Note that faces are in directional order, starting at J_AXES_DIGIT.
* This table is generated by the generatePentagonDirectionFaces script.
*/
static const PentagonDirectionFaces pentagonDirectionFaces[NUM_PENTAGONS] = {
{4, {4, 0, 2, 1, 3}}, {14, {6, 11, 2, 7, 1}},
{24, {5, 10, 1, 6, 0}}, {38, {7, 12, 3, 8, 2}},
{49, {9, 14, 0, 5, 4}}, {58, {8, 13, 4, 9, 3}},
{63, {11, 6, 15, 10, 16}}, {72, {12, 7, 16, 11, 17}},
{83, {10, 5, 19, 14, 15}}, {97, {13, 8, 17, 12, 18}},
{107, {14, 9, 18, 13, 19}}, {117, {15, 19, 17, 18, 16}},
};
/**
* Get the number of CCW rotations of the cell's vertex numbers
* compared to the directional layout of its neighbors.
* @param out Number of CCW rotations for the cell
*/
static H3Error vertexRotations(H3Index cell, int *out) {
// Get the face and other info for the origin
FaceIJK fijk;
H3Error err = _h3ToFaceIjk(cell, &fijk);
if (err) {
return err;
}
int baseCell = H3_EXPORT(getBaseCellNumber)(cell);
int cellLeadingDigit = _h3LeadingNonZeroDigit(cell);
// get the base cell face
FaceIJK baseFijk;
_baseCellToFaceIjk(baseCell, &baseFijk);
int ccwRot60 = _baseCellToCCWrot60(baseCell, fijk.face);
if (_isBaseCellPentagon(baseCell)) {
// Find the appropriate direction-to-face mapping
PentagonDirectionFaces dirFaces;
// We never hit the end condition
int p = 0;
for (; p < NUM_PENTAGONS; p++) {
if (pentagonDirectionFaces[p].baseCell == baseCell) {
dirFaces = pentagonDirectionFaces[p];
break;
}
}
if (p == NUM_PENTAGONS) {
return E_FAILED;
}
// additional CCW rotation for polar neighbors or IK neighbors
if (fijk.face != baseFijk.face &&
(_isBaseCellPolarPentagon(baseCell) ||
fijk.face ==
dirFaces.faces[IK_AXES_DIGIT - DIRECTION_INDEX_OFFSET])) {
ccwRot60 = (ccwRot60 + 1) % 6;
}
// Check whether the cell crosses a deleted pentagon subsequence
if (cellLeadingDigit == JK_AXES_DIGIT &&
fijk.face ==
dirFaces.faces[IK_AXES_DIGIT - DIRECTION_INDEX_OFFSET]) {
// Crosses from JK to IK: Rotate CW
ccwRot60 = (ccwRot60 + 5) % 6;
} else if (cellLeadingDigit == IK_AXES_DIGIT &&
fijk.face ==
dirFaces.faces[JK_AXES_DIGIT - DIRECTION_INDEX_OFFSET]) {
// Crosses from IK to JK: Rotate CCW
ccwRot60 = (ccwRot60 + 1) % 6;
}
}
*out = ccwRot60;
return E_SUCCESS;
}
/** @brief Hexagon direction to vertex number relationships (same face).
* Note that we don't use direction 0 (center).
*/
static const int directionToVertexNumHex[NUM_DIGITS] = {
INVALID_DIGIT, 3, 1, 2, 5, 4, 0};
/** @brief Pentagon direction to vertex number relationships (same face).
* Note that we don't use directions 0 (center) or 1 (deleted K axis).
*/
static const int directionToVertexNumPent[NUM_DIGITS] = {
INVALID_DIGIT, INVALID_DIGIT, 1, 2, 4, 3, 0};
/**
* Get the first vertex number for a given direction. The neighbor in this
* direction is located between this vertex number and the next number in
* sequence.
* @returns The number for the first topological vertex, or INVALID_VERTEX_NUM
* if the direction is not valid for this cell
*/
int vertexNumForDirection(const H3Index origin, const Direction direction) {
int isPent = H3_EXPORT(isPentagon)(origin);
// Check for invalid directions
if (direction == CENTER_DIGIT || direction >= INVALID_DIGIT ||
(isPent && direction == K_AXES_DIGIT))
return INVALID_VERTEX_NUM;
// Determine the vertex rotations for this cell
int rotations;
H3Error err = vertexRotations(origin, &rotations);
if (err) {
return INVALID_VERTEX_NUM;
}
// Find the appropriate vertex, rotating CCW if necessary
if (isPent) {
return (directionToVertexNumPent[direction] + NUM_PENT_VERTS -
rotations) %
NUM_PENT_VERTS;
} else {
return (directionToVertexNumHex[direction] + NUM_HEX_VERTS -
rotations) %
NUM_HEX_VERTS;
}
}
/** @brief Vertex number to hexagon direction relationships (same face).
*/
static const Direction vertexNumToDirectionHex[NUM_HEX_VERTS] = {
IJ_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT,
K_AXES_DIGIT, IK_AXES_DIGIT, I_AXES_DIGIT};
/** @brief Vertex number to pentagon direction relationships (same face).
*/
static const Direction vertexNumToDirectionPent[NUM_PENT_VERTS] = {
IJ_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, IK_AXES_DIGIT, I_AXES_DIGIT};
/**
* Get the direction for a given vertex number. This returns the direction for
* the neighbor between the given vertex number and the next number in sequence.
* @returns The direction for this vertex, or INVALID_DIGIT if the vertex
* number is invalid.
*/
Direction directionForVertexNum(const H3Index origin, const int vertexNum) {
int isPent = H3_EXPORT(isPentagon)(origin);
// Check for invalid vertexes
if (vertexNum < 0 ||
vertexNum > (isPent ? NUM_PENT_VERTS : NUM_HEX_VERTS) - 1)
return INVALID_DIGIT;
// Determine the vertex rotations for this cell
int rotations;
H3Error err = vertexRotations(origin, &rotations);
if (err) {
return INVALID_DIGIT;
}
// Find the appropriate direction, rotating CW if necessary
return isPent ? vertexNumToDirectionPent[(vertexNum + rotations) %
NUM_PENT_VERTS]
: vertexNumToDirectionHex[(vertexNum + rotations) %
NUM_HEX_VERTS];
}
/** @brief Directions in CCW order */
static const Direction DIRECTIONS[NUM_HEX_VERTS] = {
J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT,
IK_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT};
/** @brief Reverse direction from neighbor in each direction,
* given as an index into DIRECTIONS to facilitate rotation
*/
static const int revNeighborDirectionsHex[NUM_DIGITS] = {
INVALID_DIGIT, 5, 3, 4, 1, 0, 2};
/**
* Get a single vertex for a given cell, as an H3 index, or
* H3_NULL if the vertex is invalid
* @param cell Cell to get the vertex for
* @param vertexNum Number (index) of the vertex to calculate
* @param out Output: The vertex index
*/
H3Error H3_EXPORT(cellToVertex)(H3Index cell, int vertexNum, H3Index *out) {
int cellIsPentagon = H3_EXPORT(isPentagon)(cell);
int cellNumVerts = cellIsPentagon ? NUM_PENT_VERTS : NUM_HEX_VERTS;
int res = H3_GET_RESOLUTION(cell);
// Check for invalid vertexes
if (vertexNum < 0 || vertexNum > cellNumVerts - 1) return E_DOMAIN;
// Default the owner and vertex number to the input cell
H3Index owner = cell;
int ownerVertexNum = vertexNum;
// Determine the owner, looking at the three cells that share the vertex.
// By convention, the owner is the cell with the lowest numerical index.
// If the cell is the center child of its parent, it will always have
// the lowest index of any neighbor, so we can skip determining the owner
if (res == 0 || H3_GET_INDEX_DIGIT(cell, res) != CENTER_DIGIT) {
// Get the left neighbor of the vertex, with its rotations
Direction left = directionForVertexNum(cell, vertexNum);
if (left == INVALID_DIGIT) return E_FAILED;
int lRotations = 0;
H3Index leftNeighbor;
H3Error leftNeighborError =
h3NeighborRotations(cell, left, &lRotations, &leftNeighbor);
if (leftNeighborError) return leftNeighborError;
// Set to owner if lowest index
if (leftNeighbor < owner) owner = leftNeighbor;
// As above, skip the right neighbor if the left is known lowest
if (res == 0 || H3_GET_INDEX_DIGIT(leftNeighbor, res) != CENTER_DIGIT) {
// Get the right neighbor of the vertex, with its rotations
// Note that vertex - 1 is the right side, as vertex numbers are CCW
Direction right = directionForVertexNum(
cell, (vertexNum - 1 + cellNumVerts) % cellNumVerts);
// This case should be unreachable; invalid verts fail earlier
if (NEVER(right == INVALID_DIGIT)) return E_FAILED;
int rRotations = 0;
H3Index rightNeighbor;
H3Error rightNeighborError =
h3NeighborRotations(cell, right, &rRotations, &rightNeighbor);
if (rightNeighborError) return rightNeighborError;
// Set to owner if lowest index
if (rightNeighbor < owner) {
owner = rightNeighbor;
Direction dir =
H3_EXPORT(isPentagon)(owner)
? directionForNeighbor(owner, cell)
: DIRECTIONS[(revNeighborDirectionsHex[right] +
rRotations) %
NUM_HEX_VERTS];
ownerVertexNum = vertexNumForDirection(owner, dir);
}
}
// Determine the vertex number for the left neighbor
if (owner == leftNeighbor) {
int ownerIsPentagon = H3_EXPORT(isPentagon)(owner);
Direction dir =
ownerIsPentagon
? directionForNeighbor(owner, cell)
: DIRECTIONS[(revNeighborDirectionsHex[left] + lRotations) %
NUM_HEX_VERTS];
// For the left neighbor, we need the second vertex of the
// edge, which may involve looping around the vertex nums
ownerVertexNum = vertexNumForDirection(owner, dir) + 1;
if (ownerVertexNum == NUM_HEX_VERTS ||
(ownerIsPentagon && ownerVertexNum == NUM_PENT_VERTS)) {
ownerVertexNum = 0;
}
}
}
// Create the vertex index
H3Index vertex = owner;
H3_SET_MODE(vertex, H3_VERTEX_MODE);
H3_SET_RESERVED_BITS(vertex, ownerVertexNum);
*out = vertex;
return E_SUCCESS;
}
/**
* Get all vertexes for the given cell
* @param cell Cell to get the vertexes for
* @param vertexes Array to hold vertex output. Must have length >= 6.
*/
H3Error H3_EXPORT(cellToVertexes)(H3Index cell, H3Index *vertexes) {
// Get all vertexes. If the cell is a pentagon, will fill the final slot
// with H3_NULL.
bool isPent = H3_EXPORT(isPentagon)(cell);
for (int i = 0; i < NUM_HEX_VERTS; i++) {
if (i == 5 && isPent) {
vertexes[i] = H3_NULL;
} else {
H3Error cellError = H3_EXPORT(cellToVertex)(cell, i, &vertexes[i]);
if (cellError) {
return cellError;
}
}
}
return E_SUCCESS;
}
/**
* Get the geocoordinates of an H3 vertex
* @param vertex H3 index describing a vertex
* @param coord Output geo coordinate
*/
H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *coord) {
// Get the vertex number and owner from the vertex
int vertexNum = H3_GET_RESERVED_BITS(vertex);
H3Index owner = vertex;
H3_SET_MODE(owner, H3_CELL_MODE);
H3_SET_RESERVED_BITS(owner, 0);
// Get the single vertex from the boundary
CellBoundary gb;
FaceIJK fijk;
H3Error fijkError = _h3ToFaceIjk(owner, &fijk);
if (fijkError) {
return fijkError;
}
int res = H3_GET_RESOLUTION(owner);
if (H3_EXPORT(isPentagon)(owner)) {
_faceIjkPentToCellBoundary(&fijk, res, vertexNum, 1, &gb);
} else {
_faceIjkToCellBoundary(&fijk, res, vertexNum, 1, &gb);
}
// Copy from boundary to output coord
*coord = gb.verts[0];
return E_SUCCESS;
}
/**
* Whether the input is a valid H3 vertex
* @param vertex H3 index possibly describing a vertex
* @return Whether the input is valid
*/
int H3_EXPORT(isValidVertex)(H3Index vertex) {
if (H3_GET_MODE(vertex) != H3_VERTEX_MODE) {
return 0;
}
int vertexNum = H3_GET_RESERVED_BITS(vertex);
H3Index owner = vertex;
H3_SET_MODE(owner, H3_CELL_MODE);
H3_SET_RESERVED_BITS(owner, 0);
if (!H3_EXPORT(isValidCell)(owner)) {
return 0;
}
// The easiest way to ensure that the owner + vertex number is valid,
// and that the vertex is canonical, is to recreate and compare.
H3Index canonical;
if (H3_EXPORT(cellToVertex)(owner, vertexNum, &canonical)) {
return 0;
}
return vertex == canonical ? 1 : 0;
}