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; +}