Skip to content
Open
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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 61 additions & 0 deletions examples/edgeVertexes.c
Original file line number Diff line number Diff line change
@@ -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 <h3/h3api.h>
#include <inttypes.h>
#include <stdio.h>

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;
}
138 changes: 138 additions & 0 deletions src/apps/testapps/testDirectedEdge.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
}
10 changes: 10 additions & 0 deletions src/h3lib/include/h3api.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
* @{
Expand Down
53 changes: 53 additions & 0 deletions src/h3lib/lib/directedEdge.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Comment on lines +313 to +315
Copy link
Collaborator

Choose a reason for hiding this comment

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

All other error cases but the initial one are uncovered. Maybe they can be marked NEVER, which we use to mark error cases that should not be reachable by any condition?


// 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;
}
Loading