h3_directedEdge.c (171 lines of code) (raw):
/*
* Copyright 2017-2018, 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 directedEdge.c
* @brief DirectedEdge functions for manipulating directed edge indexes.
*/
#include <inttypes.h>
#include <stdbool.h>
#include "h3_algos.h"
#include "h3_constants.h"
#include "h3_coordijk.h"
#include "h3_h3Assert.h"
#include "h3_h3Index.h"
#include "h3_latLng.h"
#include "h3_vertex.h"
/**
* Returns whether or not the provided H3Indexes are neighbors.
* @param origin The origin H3 index.
* @param destination The destination H3 index.
* @param out Set to 1 if the indexes are neighbors, 0 otherwise
* @return Error code if the origin or destination are invalid or incomparable.
*/
H3Error H3_EXPORT(areNeighborCells)(H3Index origin, H3Index destination,
int *out) {
// Make sure they're hexagon indexes
if (H3_GET_MODE(origin) != H3_CELL_MODE ||
H3_GET_MODE(destination) != H3_CELL_MODE) {
return E_CELL_INVALID;
}
// Hexagons cannot be neighbors with themselves
if (origin == destination) {
*out = 0;
return E_SUCCESS;
}
// Only hexagons in the same resolution can be neighbors
if (H3_GET_RESOLUTION(origin) != H3_GET_RESOLUTION(destination)) {
return E_RES_MISMATCH;
}
// H3 Indexes that share the same parent are very likely to be neighbors
// Child 0 is neighbor with all of its parent's 'offspring', the other
// children are neighbors with 3 of the 7 children. So a simple comparison
// of origin and destination parents and then a lookup table of the children
// is a super-cheap way to possibly determine they are neighbors.
int parentRes = H3_GET_RESOLUTION(origin) - 1;
if (parentRes > 0) {
// TODO: Return error codes here
H3Index originParent;
H3_EXPORT(cellToParent)(origin, parentRes, &originParent);
H3Index destinationParent;
H3_EXPORT(cellToParent)(destination, parentRes, &destinationParent);
if (originParent == destinationParent) {
Direction originResDigit =
H3_GET_INDEX_DIGIT(origin, parentRes + 1);
Direction destinationResDigit =
H3_GET_INDEX_DIGIT(destination, parentRes + 1);
if (originResDigit == CENTER_DIGIT ||
destinationResDigit == CENTER_DIGIT) {
*out = 1;
return E_SUCCESS;
}
if (originResDigit >= INVALID_DIGIT) {
// Prevent indexing off the end of the array below
return E_CELL_INVALID;
}
if ((originResDigit == K_AXES_DIGIT ||
destinationResDigit == K_AXES_DIGIT) &&
H3_EXPORT(isPentagon)(originParent)) {
// If these are invalid cells, fail rather than incorrectly
// reporting neighbors. For pentagon cells that are actually
// neighbors across the deleted subsequence, they will fail the
// optimized check below, but they will be accepted by the
// gridDisk check below that.
return E_CELL_INVALID;
}
// These sets are the relevant neighbors in the clockwise
// and counter-clockwise
const Direction neighborSetClockwise[] = {
CENTER_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, J_AXES_DIGIT,
IK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT};
const Direction neighborSetCounterclockwise[] = {
CENTER_DIGIT, IK_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT,
IJ_AXES_DIGIT, I_AXES_DIGIT, J_AXES_DIGIT};
if (neighborSetClockwise[originResDigit] == destinationResDigit ||
neighborSetCounterclockwise[originResDigit] ==
destinationResDigit) {
*out = 1;
return E_SUCCESS;
}
}
}
// Otherwise, we have to determine the neighbor relationship the "hard" way.
H3Index neighborRing[7] = {0};
H3_EXPORT(gridDisk)(origin, 1, neighborRing);
for (int i = 0; i < 7; i++) {
if (neighborRing[i] == destination) {
*out = 1;
return E_SUCCESS;
}
}
// Made it here, they definitely aren't neighbors
*out = 0;
return E_SUCCESS;
}
/**
* Returns a directed edge H3 index based on the provided origin and
* destination
* @param origin The origin H3 hexagon index
* @param destination The destination H3 hexagon index
* @param out Output: The directed edge H3Index.
*/
H3Error H3_EXPORT(cellsToDirectedEdge)(H3Index origin, H3Index destination,
H3Index *out) {
// Determine the IJK direction from the origin to the destination
Direction direction = directionForNeighbor(origin, destination);
// The direction will be invalid if the cells are not neighbors
if (direction == INVALID_DIGIT) {
return E_NOT_NEIGHBORS;
}
// Create the edge index for the neighbor direction
H3Index output = origin;
H3_SET_MODE(output, H3_DIRECTEDEDGE_MODE);
H3_SET_RESERVED_BITS(output, direction);
*out = output;
return E_SUCCESS;
}
/**
* Returns the origin hexagon from the directed edge H3Index
* @param edge The edge H3 index
* @param out Output: The origin H3 hexagon index
*/
H3Error H3_EXPORT(getDirectedEdgeOrigin)(H3Index edge, H3Index *out) {
if (H3_GET_MODE(edge) != H3_DIRECTEDEDGE_MODE) {
return E_DIR_EDGE_INVALID;
}
H3Index origin = edge;
H3_SET_MODE(origin, H3_CELL_MODE);
H3_SET_RESERVED_BITS(origin, 0);
*out = origin;
return E_SUCCESS;
}
/**
* Returns the destination hexagon from the directed edge H3Index
* @param edge The edge H3 index
* @param out Output: The destination H3 hexagon index
*/
H3Error H3_EXPORT(getDirectedEdgeDestination)(H3Index edge, H3Index *out) {
Direction direction = H3_GET_RESERVED_BITS(edge);
int rotations = 0;
H3Index origin;
// Note: This call is also checking for H3_DIRECTEDEDGE_MODE
H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin);
if (originResult) {
return originResult;
}
return h3NeighborRotations(origin, direction, &rotations, out);
}
/**
* Determines if the provided H3Index is a valid directed edge index
* @param edge The directed edge H3Index
* @return 1 if it is a directed edge H3Index, otherwise 0.
*/
int H3_EXPORT(isValidDirectedEdge)(H3Index edge) {
Direction neighborDirection = H3_GET_RESERVED_BITS(edge);
if (neighborDirection <= CENTER_DIGIT || neighborDirection >= NUM_DIGITS) {
return 0;
}
H3Index origin;
// Note: This call is also checking for H3_DIRECTEDEDGE_MODE
H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin);
if (originResult) {
return 0;
}
if (H3_EXPORT(isPentagon)(origin) && neighborDirection == K_AXES_DIGIT) {
return 0;
}
return H3_EXPORT(isValidCell)(origin);
}
/**
* Returns the origin, destination pair of hexagon IDs for the given edge ID
* @param edge The directed edge H3Index
* @param originDestination Pointer to memory to store origin and destination
* IDs
*/
H3Error H3_EXPORT(directedEdgeToCells)(H3Index edge,
H3Index *originDestination) {
H3Error originResult =
H3_EXPORT(getDirectedEdgeOrigin)(edge, &originDestination[0]);
if (originResult) {
return originResult;
}
H3Error destinationResult =
H3_EXPORT(getDirectedEdgeDestination)(edge, &originDestination[1]);
if (destinationResult) {
return destinationResult;
}
return E_SUCCESS;
}
/**
* Provides all of the directed edges from the current H3Index.
* @param origin The origin hexagon H3Index to find edges for.
* @param edges The memory to store all of the edges inside.
*/
H3Error H3_EXPORT(originToDirectedEdges)(H3Index origin, H3Index *edges) {
// Determine if the origin is a pentagon and special treatment needed.
int isPent = H3_EXPORT(isPentagon)(origin);
// This is actually quite simple. Just modify the bits of the origin
// slightly for each direction, except the 'k' direction in pentagons,
// which is zeroed.
for (int i = 0; i < 6; i++) {
if (isPent && i == 0) {
edges[i] = H3_NULL;
} else {
edges[i] = origin;
H3_SET_MODE(edges[i], H3_DIRECTEDEDGE_MODE);
H3_SET_RESERVED_BITS(edges[i], i + 1);
}
}
return E_SUCCESS;
}
/**
* Provides the coordinates defining the directed edge.
* @param edge The directed edge H3Index
* @param cb The cellboundary object to store the edge coordinates.
*/
H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, CellBoundary *cb) {
// Get the origin and neighbor direction from the edge
Direction direction = H3_GET_RESERVED_BITS(edge);
H3Index origin;
H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin);
if (originResult) {
return originResult;
}
// Get the start vertex for the edge
int startVertex = vertexNumForDirection(origin, direction);
if (startVertex == INVALID_VERTEX_NUM) {
// This is not actually an edge (i.e. no valid direction),
// so return no vertices.
cb->numVerts = 0;
return E_DIR_EDGE_INVALID;
}
// Get the geo boundary for the appropriate vertexes of the origin. Note
// that while there are always 2 topological vertexes per edge, the
// resulting edge boundary may have an additional distortion vertex if it
// crosses an edge of the icosahedron.
FaceIJK fijk;
H3Error fijkResult = _h3ToFaceIjk(origin, &fijk);
if (NEVER(fijkResult)) {
return fijkResult;
}
int res = H3_GET_RESOLUTION(origin);
int isPent = H3_EXPORT(isPentagon)(origin);
if (isPent) {
_faceIjkPentToCellBoundary(&fijk, res, startVertex, 2, cb);
} else {
_faceIjkToCellBoundary(&fijk, res, startVertex, 2, cb);
}
return E_SUCCESS;
}