Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The public API of this library consists of the functions declared in file
## [Unreleased]

### Added
- Vertex mode and associated functions:
- `cellToVertex(cell, vertexNum)`
- `cellToVertexes(cell, out)`
- `vertexToPoint(vertex, out)`
- closed-form implementation of `numHexagons`

### Breaking changes
Expand Down
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ set(OTHER_SOURCE_FILES
src/apps/testapps/testH3SetToVertexGraph.c
src/apps/testapps/testBBox.c
src/apps/testapps/testVertex.c
src/apps/testapps/testVertexExhaustive.c
src/apps/testapps/testPolygon.c
src/apps/testapps/testVec2d.c
src/apps/testapps/testVec3d.c
Expand Down Expand Up @@ -214,6 +215,7 @@ set(OTHER_SOURCE_FILES
src/apps/benchmarks/benchmarkKRing.c
src/apps/benchmarks/benchmarkH3Line.c
src/apps/benchmarks/benchmarkH3UniEdge.c
src/apps/benchmarks/benchmarkVertex.c
src/apps/benchmarks/benchmarkH3Api.c)

set(ALL_SOURCE_FILES
Expand Down Expand Up @@ -605,6 +607,7 @@ if(H3_IS_ROOT_PROJECT AND BUILD_TESTING)
# The "Exhaustive" part of the test name is used by the test-fast to exclude these files.
# test-fast exists so that Travis CI can run Valgrind on tests without taking a very long time.
add_h3_test(testH3UniEdgeExhaustive src/apps/testapps/testH3UniEdgeExhaustive.c)
add_h3_test(testVertexExhaustive src/apps/testapps/testVertexExhaustive.c)
add_h3_test(testH3ToLocalIjExhaustive src/apps/testapps/testH3ToLocalIjExhaustive.c)
add_h3_test(testH3LineExhaustive src/apps/testapps/testH3LineExhaustive.c)
add_h3_test(testH3DistanceExhaustive src/apps/testapps/testH3DistanceExhaustive.c)
Expand Down Expand Up @@ -633,6 +636,7 @@ if(BUILD_BENCHMARKS)
add_h3_benchmark(benchmarkKRing src/apps/benchmarks/benchmarkKRing.c)
add_h3_benchmark(benchmarkH3Line src/apps/benchmarks/benchmarkH3Line.c)
add_h3_benchmark(benchmarkH3UniEdge src/apps/benchmarks/benchmarkH3UniEdge.c)
add_h3_benchmark(benchmarkVertex src/apps/benchmarks/benchmarkVertex.c)
add_h3_benchmark(benchmarkH3SetToLinkedGeo src/apps/benchmarks/benchmarkH3SetToLinkedGeo.c)
add_h3_benchmark(benchmarkPolyfill src/apps/benchmarks/benchmarkPolyfill.c)
if(ENABLE_REQUIRES_ALL_SYMBOLS)
Expand Down
49 changes: 31 additions & 18 deletions docs/core-library/h3indexing.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,57 @@ Child hexagons are linearly smaller than their parent hexagons.

## H3Index Representation

The **H3Index** is the integer representation of an **H3** index, which can be placed into multiple modes to indicate the concept being indexed.
An **H3Index** is the integer representation of an **H3** index, which may be one of multiple modes to indicate the concept being indexed.

* Mode 1 is an **H3** Cell (Hexagon/Pentagon) index.
* Mode 2 is an **H3** Unidirectional Edge (Cell A -> Cell B) index.
* Mode 3 is planned to be a bidirectional edge (Cell A <-> Cell B).
* Mode 0 is reserved and indicates an invalid **H3** index.
* This mode remains, partially, for backwards compatibility with an older version of H3.
* Mode 1 is an **H3 Cell** (Hexagon/Pentagon) index.
* Mode 2 is an **H3 Unidirectional Edge** (Cell A -> Cell B) index.
* Mode 3 is planned to be a bidirectional edge (Cell A <-> Cell B).
* Mode 4 is an **H3 Vertex** (i.e. a single vertex of an H3 Cell).

The canonical string representation of an **H3Index** is the hexadecimal representation of the integer, using lowercase letters. The string representation is variable length (no zero padding) and is not prefixed or suffixed.

### Invalid Index

Mode 0 contains a special index, `H3_NULL`, which is unique: it is bit-equivalent to `0`.
This index indicates, *specifically*, an invalid, missing, or uninitialized **H3** index;
it is analogous to `NaN` in floating point.
It should be used instead of an arbitrary Mode 0 index, due to its uniqueness and easy identifiability.

The components of the **H3** cell index (mode 1) are packed into a 64-bit integer in order, highest bit first, as follows:
### H3 Cell Index

An H3 Cell index (mode 1) represents a cell (hexagon or pentagon) in the H3 grid system at a particular resolution. The components of the H3 Cell index are packed into a 64-bit integer in order, highest bit first, as follows:

* 1 bit reserved and set to 0,
* 4 bits to indicate the index mode,
* 4 bits to indicate the H3 Cell index mode,
* 3 bits reserved and set to 0,
* 4 bits to indicate the cell resolution 0-15,
* 7 bits to indicate the base cell 0-121, and
* 7 bits to indicate the base cell 0-121,
* 3 bits to indicate each subsequent digit 0-6 from resolution 1 up to the resolution of the cell (45 bits total are reserved for resolutions 1-15)

The components of the **H3** unidirectional edge index (mode 2) are packed into a 64-bit integer in order, highest bit first, as follows:
The three bits for each unused digit are set to 7.

### H3 Unidirectional Edge Index

An H3 Undirectional Edge index (mode 2) represents a single directed edge between two cells (an "origin" cell and a neighboring "destination" cell). The components of the H3 Unidirectional Edge index are packed into a 64-bit integer in order, highest bit first, as follows:

* 1 bit reserved and set to 0,
* 4 bits to indicate the index mode,
* 3 bits to indicate the edge 1-6 of the cell to traverse,
* 4 bits to indicate the cell resolution 0-15,
* 7 bits to indicate the base cell 0-121, and
* 3 bits to indicate each subsequent digit 0-6 from resolution 1 up to the resolution of the cell (45 bits total are reserved for resolutions 1-15)
* 4 bits to indicate the H3 Unidirectional Edge index mode,
* 3 bits to indicate the edge (1-6) of the origin cell,
* Subsequent bits matching the index bits of the origin cell.

The canonical string representation of an **H3Index** is the hexadecimal representation of the integer, using lowercase letters. The string representation is variable length (no zero padding) and is not prefixed or suffixed.
### H3 Vertex Index

The three bits for each unused digit are set to 7.
An H3 Vertex index (mode 4) represents a single topological vertex in H3 grid system, shared by three cells. Note that this does not include the distortion vertexes occasionally present in a cell's geo boundary. An H3 Vertex is arbitrarily assigned one of the three neighboring cells as its "owner", which is used to calculate the canonical index and geo coordinate for the vertex. The components of the H3 Vertex index are packed into a 64-bit integer in order, highest bit first, as follows:

* 1 bit reserved and set to 0,
* 4 bits to indicate the H3 Vertex index mode,
* 3 bits to indicate the vertex number (0-5) of vertex on the owner cell,
* Subsequent bits matching the index bits of the owner cell.

## Bit layout of H3Index

The layout of an **H3Index** is shown below in table form. The interpretation of the "Reserved/edge" field differs depending on the mode of the index.
The layout of an **H3Index** is shown below in table form. The interpretation of the "Reserved" field differs depending on the mode of the index.

<table>
<tr>
Expand All @@ -76,7 +89,7 @@ The layout of an **H3Index** is shown below in table form. The interpretation of
<th>0x30</th>
<td>Reserved</td>
<td colspan="4">Mode</td>
<td colspan="3">Reserved/edge</td>
<td colspan="3">Mode-Dependent</td>
<td colspan="4">Resolution</td>
<td colspan="4">Base cell</td>
</tr>
Expand Down
3 changes: 2 additions & 1 deletion src/apps/benchmarks/benchmarkH3UniEdge.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ GeoBoundary outBoundary;
H3_EXPORT(getH3UnidirectionalEdgesFromHexagon)(hex, edges);

BENCHMARK(getH3UnidirectionalEdgeBoundary, 10000, {
for (int i = 0; i < 6; i++)
for (int i = 0; i < 6; i++) {
H3_EXPORT(getH3UnidirectionalEdgeBoundary)(edges[i], &outBoundary);
}
});

END_BENCHMARKS();
55 changes: 55 additions & 0 deletions src/apps/benchmarks/benchmarkVertex.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 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.
*/
#include "benchmark.h"
#include "h3api.h"
#include "vertex.h"

// Fixtures. Cells are arbitrary, except that ring2 is all hexagons and
// ring2Pent is centered on a pentagon.
H3Index ring2[] = {
0x89283081083ffff, 0x8928308109bffff, 0x8928308108bffff, 0x8928308108fffff,
0x89283081087ffff, 0x89283081097ffff, 0x89283081093ffff, 0x89283081467ffff,
0x8928308146fffff, 0x892830810d7ffff, 0x892830810c7ffff, 0x89283081013ffff,
0x89283081017ffff, 0x892830810bbffff, 0x892830810b3ffff, 0x8928308154bffff,
0x8928308155bffff, 0x8928308142fffff, 0x8928308142bffff};
int ring2Count = 19;

H3Index ring2Pent[] = {
0x8508008bfffffff, 0x8508000ffffffff, 0x85080077fffffff, 0x85080047fffffff,
0x85080017fffffff, 0x85080003fffffff, 0x8508000bfffffff, 0x85080073fffffff,
0x85080057fffffff, 0x850800abfffffff, 0x8508008ffffffff, 0x85080013fffffff,
0x8508001bfffffff, 0x850800c7fffffff, 0x850800cffffffff, 0x850800bbfffffff};
int ring2PentCount = 16;

BEGIN_BENCHMARKS();

H3Index* vertexes = calloc(6, sizeof(H3Index));

BENCHMARK(cellToVertexes, 10000, {
for (int i = 0; i < ring2Count; i++) {
H3_EXPORT(cellToVertexes)(ring2[i], vertexes);
}
});

BENCHMARK(cellToVertexesPent, 10000, {
for (int i = 0; i < ring2PentCount; i++) {
H3_EXPORT(cellToVertexes)(ring2Pent[i], vertexes);
}
});

free(vertexes);

END_BENCHMARKS();
30 changes: 26 additions & 4 deletions src/apps/testapps/testVertex.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 Uber Technologies, Inc.
* 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.
Expand All @@ -15,8 +15,6 @@
*/
/** @file
* @brief tests H3 vertex functions.
*
* usage: `testVertex`
*/

#include "test.h"
Expand Down Expand Up @@ -63,4 +61,28 @@ SUITE(Vertex) {
vertexNumForDirection(pentagon, K_AXES_DIGIT) == INVALID_VERTEX_NUM,
"K direction on pentagon should return invalid vertex");
}
}

TEST(directionForVertexNum_hex) {
H3Index origin = 0x823d6ffffffffff;
bool seenDirs[NUM_DIGITS] = {false};
for (int vertexNum = 0; vertexNum < NUM_HEX_VERTS; vertexNum++) {
Direction dir = directionForVertexNum(origin, vertexNum);
t_assert(dir > 0 && dir < INVALID_DIGIT, "direction appears valid");
t_assert(!seenDirs[dir], "direction appears only once");
seenDirs[dir] = true;
}
}

TEST(directionForVertexNum_badVerts) {
H3Index origin = 0x823d6ffffffffff;

t_assert(directionForVertexNum(origin, -1) == INVALID_DIGIT,
"negative vertex should return invalid direction");
t_assert(directionForVertexNum(origin, 6) == INVALID_DIGIT,
"invalid vertex should return invalid direction");

H3Index pentagon = 0x823007fffffffff;
t_assert(directionForVertexNum(pentagon, 5) == INVALID_DIGIT,
"invalid pent vertex should return invalid direction");
}
}
133 changes: 133 additions & 0 deletions src/apps/testapps/testVertexExhaustive.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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
* @brief tests H3 vertex functions, exhaustively checking all cells at res 0-4
*/

#include "test.h"
#include "utility.h"
#include "vertex.h"

static void directionForVertexNum_symmetry_assertions(H3Index h3) {
int numVerts = H3_EXPORT(h3IsPentagon)(h3) ? NUM_PENT_VERTS : NUM_HEX_VERTS;
for (int i = 0; i < numVerts; i++) {
Direction dir = directionForVertexNum(h3, i);
int vertexNum = vertexNumForDirection(h3, dir);
t_assert(
vertexNum == i,
"directionForVertexNum and vertexNumForDirection are symmetrical");
}
}

static void cellToVertex_point_assertions(H3Index h3) {
GeoBoundary gb;
H3_EXPORT(h3ToGeoBoundary)(h3, &gb);
int numVerts = H3_EXPORT(h3IsPentagon)(h3) ? NUM_PENT_VERTS : NUM_HEX_VERTS;

// This test won't work if there are distortion vertexes in the boundary
if (numVerts < gb.numVerts) return;

GeoCoord coord;
for (int i = 0; i < numVerts; i++) {
H3Index vertex = H3_EXPORT(cellToVertex)(h3, i);
H3_EXPORT(vertexToPoint)(vertex, &coord);
int almostEqual =
geoAlmostEqualThreshold(&gb.verts[i], &coord, 0.000001);
t_assert(almostEqual, "Vertex coordinates match boundary vertex");
}
}

static void cellToVertex_uniqueness_assertions(H3Index h3) {
H3Index originVerts[NUM_HEX_VERTS] = {0};
H3_EXPORT(cellToVertexes)(h3, originVerts);

for (int v1 = 0; v1 < NUM_HEX_VERTS - 1; v1++) {
for (int v2 = v1 + 1; v2 < NUM_HEX_VERTS; v2++) {
if (originVerts[v1] == originVerts[v2]) {
t_assert(false, "vertex should be unique");
}
}
}
}

static void cellToVertex_neighbor_assertions(H3Index h3) {
H3Index neighbors[7] = {0};
H3Index originVerts[NUM_HEX_VERTS] = {0};
H3Index neighborVerts[NUM_HEX_VERTS] = {0};

H3_EXPORT(kRing)(h3, 1, neighbors);
H3_EXPORT(cellToVertexes)(h3, originVerts);

for (int i = 0; i < 7; i++) {
H3Index neighbor = neighbors[i];
if (neighbor == H3_NULL || neighbor == h3) continue;
H3_EXPORT(cellToVertexes)(neighbor, neighborVerts);

// calculate the set intersection
int intersection = 0;
for (int v1 = 0; v1 < NUM_HEX_VERTS; v1++) {
for (int v2 = 0; v2 < NUM_HEX_VERTS; v2++) {
if (neighborVerts[v1] == originVerts[v2]) {
intersection++;
}
}
}

t_assert(intersection == 2,
"Neighbor shares 2 unique vertexes with origin");
}
}

SUITE(Vertex) {
TEST(directionForVertexNum_symmetry) {
iterateAllIndexesAtRes(0, directionForVertexNum_symmetry_assertions);
iterateAllIndexesAtRes(1, directionForVertexNum_symmetry_assertions);
iterateAllIndexesAtRes(2, directionForVertexNum_symmetry_assertions);
iterateAllIndexesAtRes(3, directionForVertexNum_symmetry_assertions);
iterateAllIndexesAtRes(4, directionForVertexNum_symmetry_assertions);
}

TEST(cellToVertex_point) {
iterateAllIndexesAtRes(0, cellToVertex_point_assertions);
iterateAllIndexesAtRes(1, cellToVertex_point_assertions);
iterateAllIndexesAtRes(2, cellToVertex_point_assertions);
iterateAllIndexesAtRes(3, cellToVertex_point_assertions);
iterateAllIndexesAtRes(4, cellToVertex_point_assertions);

// Res 5: normal base cell
iterateBaseCellIndexesAtRes(5, cellToVertex_point_assertions, 0);
// Res 5: pentagon base cell
iterateBaseCellIndexesAtRes(5, cellToVertex_point_assertions, 14);
// Res 5: polar pentagon base cell
iterateBaseCellIndexesAtRes(5, cellToVertex_point_assertions, 117);
}

TEST(cellToVertex_neighbors) {
iterateAllIndexesAtRes(0, cellToVertex_neighbor_assertions);
iterateAllIndexesAtRes(1, cellToVertex_neighbor_assertions);
iterateAllIndexesAtRes(2, cellToVertex_neighbor_assertions);
iterateAllIndexesAtRes(3, cellToVertex_neighbor_assertions);
iterateAllIndexesAtRes(4, cellToVertex_neighbor_assertions);
}

TEST(cellToVertex_uniqueness) {
iterateAllIndexesAtRes(0, cellToVertex_uniqueness_assertions);
iterateAllIndexesAtRes(1, cellToVertex_uniqueness_assertions);
iterateAllIndexesAtRes(2, cellToVertex_uniqueness_assertions);
iterateAllIndexesAtRes(3, cellToVertex_uniqueness_assertions);
iterateAllIndexesAtRes(4, cellToVertex_uniqueness_assertions);
}
}
3 changes: 3 additions & 0 deletions src/h3lib/include/algos.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
// neighbor along the ijk coordinate system of the current face, rotated
H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations);

// IJK direction of neighbor
Direction directionForNeighbor(H3Index origin, H3Index destination);

// k-ring implementation
void _kRingInternal(H3Index origin, int k, H3Index* out, int* distances,
int maxIdx, int curK);
Expand Down
2 changes: 2 additions & 0 deletions src/h3lib/include/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,7 @@
/** H3 index modes */
#define H3_HEXAGON_MODE 1
#define H3_UNIEDGE_MODE 2
#define H3_EDGE_MODE 3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the edge mode is reserved I presume?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - I added H3_EDGE_MODE proactively because it made more sense for it to be sequentially next to H3_UNIEDGE_MODE, and we're pretty sure we're going to add it. Could remove here, I just thought it would look funny to have H3_UNIEDGE_MODE, H3_VERTEX_MODE, and H3_EDGE_MODE in that order.

#define H3_VERTEX_MODE 4
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the new mode to the specification in docs

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the Indexing docs, reformatting, clarifying a bit, and adding the new mode.


#endif
Loading