h3_coordijk.c (392 lines of code) (raw):
/*
* Copyright 2016-2018, 2020-2023 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 coordijk.c
* @brief Hex IJK coordinate systems functions including conversions to/from
* lat/lng.
*/
#include "h3_coordijk.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "h3_constants.h"
#include "h3_h3Assert.h"
#include "h3_latLng.h"
#include "h3_mathExtensions.h"
#define INT32_MAX_3 (INT32_MAX / 3)
/**
* Sets an IJK coordinate to the specified component values.
*
* @param ijk The IJK coordinate to set.
* @param i The desired i component value.
* @param j The desired j component value.
* @param k The desired k component value.
*/
void _setIJK(CoordIJK *ijk, int i, int j, int k) {
ijk->i = i;
ijk->j = j;
ijk->k = k;
}
/**
* Determine the containing hex in ijk+ coordinates for a 2D cartesian
* coordinate vector (from DGGRID).
*
* @param v The 2D cartesian coordinate vector.
* @param h The ijk+ coordinates of the containing hex.
*/
void _hex2dToCoordIJK(const Vec2d *v, CoordIJK *h) {
double a1, a2;
double x1, x2;
int m1, m2;
double r1, r2;
// quantize into the ij system and then normalize
h->k = 0;
a1 = fabsl(v->x);
a2 = fabsl(v->y);
// first do a reverse conversion
x2 = a2 * M_RSIN60;
x1 = a1 + x2 / 2.0;
// check if we have the center of a hex
m1 = (int)x1;
m2 = (int)x2;
// otherwise round correctly
r1 = x1 - m1;
r2 = x2 - m2;
if (r1 < 0.5) {
if (r1 < 1.0 / 3.0) {
if (r2 < (1.0 + r1) / 2.0) {
h->i = m1;
h->j = m2;
} else {
h->i = m1;
h->j = m2 + 1;
}
} else {
if (r2 < (1.0 - r1)) {
h->j = m2;
} else {
h->j = m2 + 1;
}
if ((1.0 - r1) <= r2 && r2 < (2.0 * r1)) {
h->i = m1 + 1;
} else {
h->i = m1;
}
}
} else {
if (r1 < 2.0 / 3.0) {
if (r2 < (1.0 - r1)) {
h->j = m2;
} else {
h->j = m2 + 1;
}
if ((2.0 * r1 - 1.0) < r2 && r2 < (1.0 - r1)) {
h->i = m1;
} else {
h->i = m1 + 1;
}
} else {
if (r2 < (r1 / 2.0)) {
h->i = m1 + 1;
h->j = m2;
} else {
h->i = m1 + 1;
h->j = m2 + 1;
}
}
}
// now fold across the axes if necessary
if (v->x < 0.0) {
if ((h->j % 2) == 0) // even
{
long long int axisi = h->j / 2;
long long int diff = h->i - axisi;
h->i = (int)(h->i - 2.0 * diff);
} else {
long long int axisi = (h->j + 1) / 2;
long long int diff = h->i - axisi;
h->i = (int)(h->i - (2.0 * diff + 1));
}
}
if (v->y < 0.0) {
h->i = h->i - (2 * h->j + 1) / 2;
h->j = -1 * h->j;
}
_ijkNormalize(h);
}
/**
* Find the center point in 2D cartesian coordinates of a hex.
*
* @param h The ijk coordinates of the hex.
* @param v The 2D cartesian coordinates of the hex center point.
*/
void _ijkToHex2d(const CoordIJK *h, Vec2d *v) {
int i = h->i - h->k;
int j = h->j - h->k;
v->x = i - 0.5 * j;
v->y = j * M_SQRT3_2;
}
/**
* Returns whether or not two ijk coordinates contain exactly the same
* component values.
*
* @param c1 The first set of ijk coordinates.
* @param c2 The second set of ijk coordinates.
* @return 1 if the two addresses match, 0 if they do not.
*/
int _ijkMatches(const CoordIJK *c1, const CoordIJK *c2) {
return (c1->i == c2->i && c1->j == c2->j && c1->k == c2->k);
}
/**
* Add two ijk coordinates.
*
* @param h1 The first set of ijk coordinates.
* @param h2 The second set of ijk coordinates.
* @param sum The sum of the two sets of ijk coordinates.
*/
void _ijkAdd(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *sum) {
sum->i = h1->i + h2->i;
sum->j = h1->j + h2->j;
sum->k = h1->k + h2->k;
}
/**
* Subtract two ijk coordinates.
*
* @param h1 The first set of ijk coordinates.
* @param h2 The second set of ijk coordinates.
* @param diff The difference of the two sets of ijk coordinates (h1 - h2).
*/
void _ijkSub(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *diff) {
diff->i = h1->i - h2->i;
diff->j = h1->j - h2->j;
diff->k = h1->k - h2->k;
}
/**
* Uniformly scale ijk coordinates by a scalar. Works in place.
*
* @param c The ijk coordinates to scale.
* @param factor The scaling factor.
*/
void _ijkScale(CoordIJK *c, int factor) {
c->i *= factor;
c->j *= factor;
c->k *= factor;
}
/**
* Returns true if _ijkNormalize with the given input could have a signed
* integer overflow. Assumes k is set to 0.
*/
bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk) {
// Check for the possibility of overflow
int max, min;
if (ijk->i > ijk->j) {
max = ijk->i;
min = ijk->j;
} else {
max = ijk->j;
min = ijk->i;
}
if (min < 0) {
// Only if the min is less than 0 will the resulting number be larger
// than max. If min is positive, then max is also positive, and a
// positive signed integer minus another positive signed integer will
// not overflow.
if (ADD_INT32S_OVERFLOWS(max, min)) {
// max + min would overflow
return true;
}
if (SUB_INT32S_OVERFLOWS(0, min)) {
// 0 - INT32_MIN would overflow
return true;
}
if (SUB_INT32S_OVERFLOWS(max, min)) {
// max - min would overflow
return true;
}
}
return false;
}
/**
* Normalizes ijk coordinates by setting the components to the smallest possible
* values. Works in place.
*
* This function does not protect against signed integer overflow. The caller
* must ensure that none of (i - j), (i - k), (j - i), (j - k), (k - i), (k - j)
* will overflow. This function may be changed in the future to make that check
* itself and return an error code.
*
* @param c The ijk coordinates to normalize.
*/
void _ijkNormalize(CoordIJK *c) {
// remove any negative values
if (c->i < 0) {
c->j -= c->i;
c->k -= c->i;
c->i = 0;
}
if (c->j < 0) {
c->i -= c->j;
c->k -= c->j;
c->j = 0;
}
if (c->k < 0) {
c->i -= c->k;
c->j -= c->k;
c->k = 0;
}
// remove the min value if needed
int min = c->i;
if (c->j < min) min = c->j;
if (c->k < min) min = c->k;
if (min > 0) {
c->i -= min;
c->j -= min;
c->k -= min;
}
}
/**
* Determines the H3 digit corresponding to a unit vector or the zero vector
* in ijk coordinates.
*
* @param ijk The ijk coordinates; must be a unit vector or zero vector.
* @return The H3 digit (0-6) corresponding to the ijk unit vector, zero vector,
* or INVALID_DIGIT (7) on failure.
*/
Direction _unitIjkToDigit(const CoordIJK *ijk) {
CoordIJK c = *ijk;
_ijkNormalize(&c);
Direction digit = INVALID_DIGIT;
for (Direction i = CENTER_DIGIT; i < NUM_DIGITS; i++) {
if (_ijkMatches(&c, &UNIT_VECS[i])) {
digit = i;
break;
}
}
return digit;
}
/**
* Returns non-zero if _upAp7 with the given input could have a signed integer
* overflow.
*
* Assumes ijk is IJK+ coordinates (no negative numbers).
*/
H3Error _upAp7Checked(CoordIJK *ijk) {
// Doesn't need to be checked because i, j, and k must all be non-negative
int i = ijk->i - ijk->k;
int j = ijk->j - ijk->k;
// <0 is checked because the input must all be non-negative, but some
// negative inputs are used in unit tests to exercise the below.
if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) {
if (ADD_INT32S_OVERFLOWS(i, i)) {
return E_FAILED;
}
int i2 = i + i;
if (ADD_INT32S_OVERFLOWS(i2, i)) {
return E_FAILED;
}
int i3 = i2 + i;
if (ADD_INT32S_OVERFLOWS(j, j)) {
return E_FAILED;
}
int j2 = j + j;
if (SUB_INT32S_OVERFLOWS(i3, j)) {
return E_FAILED;
}
if (ADD_INT32S_OVERFLOWS(i, j2)) {
return E_FAILED;
}
}
ijk->i = (int)lround(((i * 3) - j) * M_ONESEVENTH);
ijk->j = (int)lround((i + (j * 2)) * M_ONESEVENTH);
ijk->k = 0;
// Expected not to be reachable, because max + min or max - min would need
// to overflow.
if (NEVER(_ijkNormalizeCouldOverflow(ijk))) {
return E_FAILED;
}
_ijkNormalize(ijk);
return E_SUCCESS;
}
/**
* Returns non-zero if _upAp7r with the given input could have a signed integer
* overflow.
*
* Assumes ijk is IJK+ coordinates (no negative numbers).
*/
H3Error _upAp7rChecked(CoordIJK *ijk) {
// Doesn't need to be checked because i, j, and k must all be non-negative
int i = ijk->i - ijk->k;
int j = ijk->j - ijk->k;
// <0 is checked because the input must all be non-negative, but some
// negative inputs are used in unit tests to exercise the below.
if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) {
if (ADD_INT32S_OVERFLOWS(i, i)) {
return E_FAILED;
}
int i2 = i + i;
if (ADD_INT32S_OVERFLOWS(j, j)) {
return E_FAILED;
}
int j2 = j + j;
if (ADD_INT32S_OVERFLOWS(j2, j)) {
return E_FAILED;
}
int j3 = j2 + j;
if (ADD_INT32S_OVERFLOWS(i2, j)) {
return E_FAILED;
}
if (SUB_INT32S_OVERFLOWS(j3, i)) {
return E_FAILED;
}
}
ijk->i = (int)lround(((i * 2) + j) * M_ONESEVENTH);
ijk->j = (int)lround(((j * 3) - i) * M_ONESEVENTH);
ijk->k = 0;
// Expected not to be reachable, because max + min or max - min would need
// to overflow.
if (NEVER(_ijkNormalizeCouldOverflow(ijk))) {
return E_FAILED;
}
_ijkNormalize(ijk);
return E_SUCCESS;
}
/**
* Find the normalized ijk coordinates of the indexing parent of a cell in a
* counter-clockwise aperture 7 grid. Works in place.
*
* @param ijk The ijk coordinates.
*/
void _upAp7(CoordIJK *ijk) {
// convert to CoordIJ
int i = ijk->i - ijk->k;
int j = ijk->j - ijk->k;
ijk->i = (int)lround((3 * i - j) * M_ONESEVENTH);
ijk->j = (int)lround((i + 2 * j) * M_ONESEVENTH);
ijk->k = 0;
_ijkNormalize(ijk);
}
/**
* Find the normalized ijk coordinates of the indexing parent of a cell in a
* clockwise aperture 7 grid. Works in place.
*
* @param ijk The ijk coordinates.
*/
void _upAp7r(CoordIJK *ijk) {
// convert to CoordIJ
int i = ijk->i - ijk->k;
int j = ijk->j - ijk->k;
ijk->i = (int)lround((2 * i + j) * M_ONESEVENTH);
ijk->j = (int)lround((3 * j - i) * M_ONESEVENTH);
ijk->k = 0;
_ijkNormalize(ijk);
}
/**
* Find the normalized ijk coordinates of the hex centered on the indicated
* hex at the next finer aperture 7 counter-clockwise resolution. Works in
* place.
*
* @param ijk The ijk coordinates.
*/
void _downAp7(CoordIJK *ijk) {
// res r unit vectors in res r+1
CoordIJK iVec = {3, 0, 1};
CoordIJK jVec = {1, 3, 0};
CoordIJK kVec = {0, 1, 3};
_ijkScale(&iVec, ijk->i);
_ijkScale(&jVec, ijk->j);
_ijkScale(&kVec, ijk->k);
_ijkAdd(&iVec, &jVec, ijk);
_ijkAdd(ijk, &kVec, ijk);
_ijkNormalize(ijk);
}
/**
* Find the normalized ijk coordinates of the hex centered on the indicated
* hex at the next finer aperture 7 clockwise resolution. Works in place.
*
* @param ijk The ijk coordinates.
*/
void _downAp7r(CoordIJK *ijk) {
// res r unit vectors in res r+1
CoordIJK iVec = {3, 1, 0};
CoordIJK jVec = {0, 3, 1};
CoordIJK kVec = {1, 0, 3};
_ijkScale(&iVec, ijk->i);
_ijkScale(&jVec, ijk->j);
_ijkScale(&kVec, ijk->k);
_ijkAdd(&iVec, &jVec, ijk);
_ijkAdd(ijk, &kVec, ijk);
_ijkNormalize(ijk);
}
/**
* Find the normalized ijk coordinates of the hex in the specified digit
* direction from the specified ijk coordinates. Works in place.
*
* @param ijk The ijk coordinates.
* @param digit The digit direction from the original ijk coordinates.
*/
void _neighbor(CoordIJK *ijk, Direction digit) {
if (digit > CENTER_DIGIT && digit < NUM_DIGITS) {
_ijkAdd(ijk, &UNIT_VECS[digit], ijk);
_ijkNormalize(ijk);
}
}
/**
* Rotates ijk coordinates 60 degrees counter-clockwise. Works in place.
*
* @param ijk The ijk coordinates.
*/
void _ijkRotate60ccw(CoordIJK *ijk) {
// unit vector rotations
CoordIJK iVec = {1, 1, 0};
CoordIJK jVec = {0, 1, 1};
CoordIJK kVec = {1, 0, 1};
_ijkScale(&iVec, ijk->i);
_ijkScale(&jVec, ijk->j);
_ijkScale(&kVec, ijk->k);
_ijkAdd(&iVec, &jVec, ijk);
_ijkAdd(ijk, &kVec, ijk);
_ijkNormalize(ijk);
}
/**
* Rotates ijk coordinates 60 degrees clockwise. Works in place.
*
* @param ijk The ijk coordinates.
*/
void _ijkRotate60cw(CoordIJK *ijk) {
// unit vector rotations
CoordIJK iVec = {1, 0, 1};
CoordIJK jVec = {1, 1, 0};
CoordIJK kVec = {0, 1, 1};
_ijkScale(&iVec, ijk->i);
_ijkScale(&jVec, ijk->j);
_ijkScale(&kVec, ijk->k);
_ijkAdd(&iVec, &jVec, ijk);
_ijkAdd(ijk, &kVec, ijk);
_ijkNormalize(ijk);
}
/**
* Rotates indexing digit 60 degrees counter-clockwise. Returns result.
*
* @param digit Indexing digit (between 1 and 6 inclusive)
*/
Direction _rotate60ccw(Direction digit) {
switch (digit) {
case K_AXES_DIGIT:
return IK_AXES_DIGIT;
case IK_AXES_DIGIT:
return I_AXES_DIGIT;
case I_AXES_DIGIT:
return IJ_AXES_DIGIT;
case IJ_AXES_DIGIT:
return J_AXES_DIGIT;
case J_AXES_DIGIT:
return JK_AXES_DIGIT;
case JK_AXES_DIGIT:
return K_AXES_DIGIT;
default:
return digit;
}
}
/**
* Rotates indexing digit 60 degrees clockwise. Returns result.
*
* @param digit Indexing digit (between 1 and 6 inclusive)
*/
Direction _rotate60cw(Direction digit) {
switch (digit) {
case K_AXES_DIGIT:
return JK_AXES_DIGIT;
case JK_AXES_DIGIT:
return J_AXES_DIGIT;
case J_AXES_DIGIT:
return IJ_AXES_DIGIT;
case IJ_AXES_DIGIT:
return I_AXES_DIGIT;
case I_AXES_DIGIT:
return IK_AXES_DIGIT;
case IK_AXES_DIGIT:
return K_AXES_DIGIT;
default:
return digit;
}
}
/**
* Find the normalized ijk coordinates of the hex centered on the indicated
* hex at the next finer aperture 3 counter-clockwise resolution. Works in
* place.
*
* @param ijk The ijk coordinates.
*/
void _downAp3(CoordIJK *ijk) {
// res r unit vectors in res r+1
CoordIJK iVec = {2, 0, 1};
CoordIJK jVec = {1, 2, 0};
CoordIJK kVec = {0, 1, 2};
_ijkScale(&iVec, ijk->i);
_ijkScale(&jVec, ijk->j);
_ijkScale(&kVec, ijk->k);
_ijkAdd(&iVec, &jVec, ijk);
_ijkAdd(ijk, &kVec, ijk);
_ijkNormalize(ijk);
}
/**
* Find the normalized ijk coordinates of the hex centered on the indicated
* hex at the next finer aperture 3 clockwise resolution. Works in place.
*
* @param ijk The ijk coordinates.
*/
void _downAp3r(CoordIJK *ijk) {
// res r unit vectors in res r+1
CoordIJK iVec = {2, 1, 0};
CoordIJK jVec = {0, 2, 1};
CoordIJK kVec = {1, 0, 2};
_ijkScale(&iVec, ijk->i);
_ijkScale(&jVec, ijk->j);
_ijkScale(&kVec, ijk->k);
_ijkAdd(&iVec, &jVec, ijk);
_ijkAdd(ijk, &kVec, ijk);
_ijkNormalize(ijk);
}
/**
* Finds the distance between the two coordinates. Returns result.
*
* @param c1 The first set of ijk coordinates.
* @param c2 The second set of ijk coordinates.
*/
int ijkDistance(const CoordIJK *c1, const CoordIJK *c2) {
CoordIJK diff;
_ijkSub(c1, c2, &diff);
_ijkNormalize(&diff);
CoordIJK absDiff = {abs(diff.i), abs(diff.j), abs(diff.k)};
return MAX(absDiff.i, MAX(absDiff.j, absDiff.k));
}
/**
* Transforms coordinates from the IJK+ coordinate system to the IJ coordinate
* system.
*
* @param ijk The input IJK+ coordinates
* @param ij The output IJ coordinates
*/
void ijkToIj(const CoordIJK *ijk, CoordIJ *ij) {
ij->i = ijk->i - ijk->k;
ij->j = ijk->j - ijk->k;
}
/**
* Transforms coordinates from the IJ coordinate system to the IJK+ coordinate
* system.
*
* @param ij The input IJ coordinates
* @param ijk The output IJK+ coordinates
* @returns E_SUCCESS on success, E_FAILED if signed integer overflow would have
* occurred.
*/
H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk) {
ijk->i = ij->i;
ijk->j = ij->j;
ijk->k = 0;
if (_ijkNormalizeCouldOverflow(ijk)) {
return E_FAILED;
}
_ijkNormalize(ijk);
return E_SUCCESS;
}
/**
* Convert IJK coordinates to cube coordinates, in place
* @param ijk Coordinate to convert
*/
void ijkToCube(CoordIJK *ijk) {
ijk->i = -ijk->i + ijk->k;
ijk->j = ijk->j - ijk->k;
ijk->k = -ijk->i - ijk->j;
}
/**
* Convert cube coordinates to IJK coordinates, in place
* @param ijk Coordinate to convert
*/
void cubeToIjk(CoordIJK *ijk) {
ijk->i = -ijk->i;
ijk->k = 0;
_ijkNormalize(ijk);
}