diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7eb6dbddb..16aa1c68c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -202,7 +202,7 @@ set(TEST_APP_SOURCE_FILES src/apps/applib/include/test.h
src/apps/applib/lib/test.c)
set(EXAMPLE_SOURCE_FILES
examples/index.c examples/distance.c examples/neighbors.c
- examples/compactCells.c examples/edge.c)
+ examples/compactCells.c examples/edge.c examples/edgeVertexes.c)
set(H3_BIN_SOURCE_FILES src/apps/filters/h3.c)
set(OTHER_SOURCE_FILES
src/apps/filters/cellToLatLng.c
diff --git a/examples/edgeVertexes.c b/examples/edgeVertexes.c
new file mode 100644
index 000000000..0ff27e6d0
--- /dev/null
+++ b/examples/edgeVertexes.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2024 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.
+ */
+/**
+ * Example program that finds the edge between two indexes and prints their
+ * start and end vertexes.
+ */
+
+#include
+#include
+#include
+
+int main(int argc, char *argv[]) {
+ H3Index origin = 0x8a2a1072b59ffffL;
+ H3Index destination = 0x8a2a1072b597fffL; // north of the origin
+
+ H3Index edge;
+ cellsToDirectedEdge(origin, destination, &edge);
+ printf("The edge is %" PRIx64 "\n", edge);
+
+ // Get the vertexes for this edge
+ H3Index vertexes[2];
+ directedEdgeToVertexes(edge, vertexes);
+
+ printf("Start vertex: %" PRIx64 "\n", vertexes[0]);
+ printf("End vertex: %" PRIx64 "\n", vertexes[1]);
+
+ // Get the coordinates of the vertexes
+ LatLng v1Coord, v2Coord;
+ vertexToLatLng(vertexes[0], &v1Coord);
+ vertexToLatLng(vertexes[1], &v2Coord);
+
+ printf("Start vertex coordinates: %lf, %lf\n",
+ radsToDegs(v1Coord.lat), radsToDegs(v1Coord.lng));
+ printf("End vertex coordinates: %lf, %lf\n",
+ radsToDegs(v2Coord.lat), radsToDegs(v2Coord.lng));
+
+ // Compare with edge boundary
+ CellBoundary boundary;
+ directedEdgeToBoundary(edge, &boundary);
+ printf("\nEdge boundary has %d vertices:\n", boundary.numVerts);
+ for (int v = 0; v < boundary.numVerts; v++) {
+ printf(" Boundary vertex #%d: %lf, %lf\n", v,
+ radsToDegs(boundary.verts[v].lat),
+ radsToDegs(boundary.verts[v].lng));
+ }
+
+ return 0;
+}
diff --git a/src/apps/testapps/testDirectedEdge.c b/src/apps/testapps/testDirectedEdge.c
index 89fcfc418..b77b5db4e 100644
--- a/src/apps/testapps/testDirectedEdge.c
+++ b/src/apps/testapps/testDirectedEdge.c
@@ -468,4 +468,142 @@ SUITE(directedEdge) {
t_assert(H3_EXPORT(edgeLengthRads)(h3, &length) == E_DIR_EDGE_INVALID,
"Non-edge (cell) has zero edge length");
}
+
+ TEST(directedEdgeToVertexes) {
+ H3Index sf;
+ t_assertSuccess(H3_EXPORT(latLngToCell)(&sfGeo, 9, &sf));
+ H3Index ring[7] = {0};
+ t_assertSuccess(H3_EXPORT(gridRingUnsafe)(sf, 1, ring));
+
+ // Get an edge from sf to one of its neighbors
+ H3Index edge;
+ t_assertSuccess(H3_EXPORT(cellsToDirectedEdge)(sf, ring[0], &edge));
+
+ // Get the vertexes for this edge
+ H3Index vertexes[2];
+ t_assertSuccess(H3_EXPORT(directedEdgeToVertexes)(edge, vertexes));
+
+ // Verify both vertexes are valid
+ t_assert(H3_EXPORT(isValidVertex)(vertexes[0]),
+ "first vertex is valid");
+ t_assert(H3_EXPORT(isValidVertex)(vertexes[1]),
+ "second vertex is valid");
+
+ // Verify the vertexes are different
+ t_assert(vertexes[0] != vertexes[1],
+ "start and end vertexes are different");
+
+ // Get all vertexes of the origin cell
+ H3Index cellVertexes[6];
+ t_assertSuccess(H3_EXPORT(cellToVertexes)(sf, cellVertexes));
+
+ // Verify that both edge vertexes are among the cell's vertexes
+ int foundStart = 0;
+ int foundEnd = 0;
+ for (int i = 0; i < 6; i++) {
+ if (cellVertexes[i] == vertexes[0]) foundStart = 1;
+ if (cellVertexes[i] == vertexes[1]) foundEnd = 1;
+ }
+ t_assert(foundStart, "start vertex is a vertex of the origin cell");
+ t_assert(foundEnd, "end vertex is a vertex of the origin cell");
+ }
+
+ TEST(directedEdgeToVertexes_pentagon) {
+ // Test with a pentagon
+ H3Index pentagon;
+ t_assertSuccess(H3_EXPORT(getPentagons)(9, &pentagon));
+
+ // Get edges for the pentagon
+ H3Index edges[6];
+ t_assertSuccess(H3_EXPORT(originToDirectedEdges)(pentagon, edges));
+
+ // Test the first valid edge (edges[0] is H3_NULL for pentagons)
+ H3Index vertexes[2];
+ t_assertSuccess(
+ H3_EXPORT(directedEdgeToVertexes)(edges[1], vertexes));
+
+ // Verify both vertexes are valid
+ t_assert(H3_EXPORT(isValidVertex)(vertexes[0]),
+ "pentagon first vertex is valid");
+ t_assert(H3_EXPORT(isValidVertex)(vertexes[1]),
+ "pentagon second vertex is valid");
+
+ // Verify the vertexes are different
+ t_assert(vertexes[0] != vertexes[1],
+ "pentagon start and end vertexes are different");
+ }
+
+ TEST(directedEdgeToVertexes_invalid) {
+ H3Index vertexes[2];
+
+ // Test with invalid edge (H3_NULL)
+ t_assert(H3_EXPORT(directedEdgeToVertexes)(H3_NULL, vertexes) ==
+ E_DIR_EDGE_INVALID,
+ "directedEdgeToVertexes fails on H3_NULL");
+
+ // Test with a cell instead of an edge
+ H3Index sf;
+ t_assertSuccess(H3_EXPORT(latLngToCell)(&sfGeo, 9, &sf));
+ t_assert(H3_EXPORT(directedEdgeToVertexes)(sf, vertexes) ==
+ E_DIR_EDGE_INVALID,
+ "directedEdgeToVertexes fails on cell");
+
+ // Test with invalid edge
+ H3Index invalidEdge = sf;
+ H3_SET_MODE(invalidEdge, H3_DIRECTEDEDGE_MODE);
+ H3_SET_RESERVED_BITS(invalidEdge, 0); // Invalid direction
+ t_assert(H3_EXPORT(directedEdgeToVertexes)(invalidEdge, vertexes) ==
+ E_DIR_EDGE_INVALID,
+ "directedEdgeToVertexes fails on invalid direction");
+ }
+
+ TEST(directedEdgeToVertexes_ordering) {
+ // Test that vertexes are ordered according to right-hand rule
+ H3Index sf;
+ t_assertSuccess(H3_EXPORT(latLngToCell)(&sfGeo, 9, &sf));
+
+ // Get all edges from the origin
+ H3Index edges[6];
+ t_assertSuccess(H3_EXPORT(originToDirectedEdges)(sf, edges));
+
+ // For each edge, verify the vertex ordering is consistent
+ for (int i = 0; i < 6; i++) {
+ if (edges[i] == H3_NULL) continue;
+
+ H3Index vertexes[2];
+ t_assertSuccess(
+ H3_EXPORT(directedEdgeToVertexes)(edges[i], vertexes));
+
+ // Get the edge boundary to compare
+ CellBoundary boundary;
+ t_assertSuccess(
+ H3_EXPORT(directedEdgeToBoundary)(edges[i], &boundary));
+
+ // The boundary should have at least 2 vertices
+ t_assert(boundary.numVerts >= 2,
+ "edge boundary has at least 2 vertices");
+
+ // Get coordinates of the vertexes
+ LatLng v1Coord, v2Coord;
+ t_assertSuccess(H3_EXPORT(vertexToLatLng)(vertexes[0], &v1Coord));
+ t_assertSuccess(H3_EXPORT(vertexToLatLng)(vertexes[1], &v2Coord));
+
+ // The first vertex should match the first boundary point
+ // (within floating point tolerance)
+ double latDiff1 =
+ v1Coord.lat - boundary.verts[0].lat;
+ double lngDiff1 =
+ v1Coord.lng - boundary.verts[0].lng;
+ t_assert(latDiff1 * latDiff1 + lngDiff1 * lngDiff1 < 0.0001,
+ "first vertex matches first boundary point");
+
+ // The second vertex should match the last boundary point
+ double latDiff2 =
+ v2Coord.lat - boundary.verts[boundary.numVerts - 1].lat;
+ double lngDiff2 =
+ v2Coord.lng - boundary.verts[boundary.numVerts - 1].lng;
+ t_assert(latDiff2 * latDiff2 + lngDiff2 * lngDiff2 < 0.0001,
+ "second vertex matches last boundary point");
+ }
+ }
}
diff --git a/src/h3lib/include/h3api.h.in b/src/h3lib/include/h3api.h.in
index dfca73362..7feb39e94 100644
--- a/src/h3lib/include/h3api.h.in
+++ b/src/h3lib/include/h3api.h.in
@@ -771,6 +771,16 @@ DECLSPEC H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge,
CellBoundary *gb);
/** @} */
+/** @defgroup directedEdgeToVertexes directedEdgeToVertexes
+ * Functions for directedEdgeToVertexes
+ * @{
+ */
+/** @brief Returns the start and end vertexes of a directed edge, ordered
+ * according to the right-hand rule */
+DECLSPEC H3Error H3_EXPORT(directedEdgeToVertexes)(H3Index edge,
+ H3Index *vertexes);
+/** @} */
+
/** @defgroup cellToVertex cellToVertex
* Functions for cellToVertex
* @{
diff --git a/src/h3lib/lib/directedEdge.c b/src/h3lib/lib/directedEdge.c
index f77436bb9..7fab94416 100644
--- a/src/h3lib/lib/directedEdge.c
+++ b/src/h3lib/lib/directedEdge.c
@@ -292,3 +292,56 @@ H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, CellBoundary *cb) {
}
return E_SUCCESS;
}
+
+/**
+ * Returns the start and end vertexes of a directed edge, ordered according
+ * to the right-hand rule.
+ * @param edge The directed edge H3Index
+ * @param vertexes Output array to store the two vertex H3Indexes. Must have
+ * length >= 2.
+ * @return E_SUCCESS on success, or an error code on failure
+ */
+H3Error H3_EXPORT(directedEdgeToVertexes)(H3Index edge, H3Index *vertexes) {
+ // Validate that this is a directed edge
+ if (!H3_EXPORT(isValidDirectedEdge)(edge)) {
+ return E_DIR_EDGE_INVALID;
+ }
+
+ // Get the origin cell from the edge
+ H3Index origin;
+ H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin);
+ if (originResult) {
+ return originResult;
+ }
+
+ // Get the direction from the edge
+ Direction direction = H3_GET_RESERVED_BITS(edge);
+
+ // Get the first vertex number for this direction
+ int startVertexNum = vertexNumForDirection(origin, direction);
+ if (startVertexNum == INVALID_VERTEX_NUM) {
+ return E_DIR_EDGE_INVALID;
+ }
+
+ // Determine if the origin is a pentagon
+ int isPent = H3_EXPORT(isPentagon)(origin);
+ int numVerts = isPent ? NUM_PENT_VERTS : NUM_HEX_VERTS;
+
+ // The second vertex is the next one in CCW order
+ int endVertexNum = (startVertexNum + 1) % numVerts;
+
+ // Convert vertex numbers to H3Index vertexes
+ H3Error startResult =
+ H3_EXPORT(cellToVertex)(origin, startVertexNum, &vertexes[0]);
+ if (startResult) {
+ return startResult;
+ }
+
+ H3Error endResult =
+ H3_EXPORT(cellToVertex)(origin, endVertexNum, &vertexes[1]);
+ if (endResult) {
+ return endResult;
+ }
+
+ return E_SUCCESS;
+}