Skip to content

Commit

Permalink
Implement the traversal subcommands for the H3 cli (#839)
Browse files Browse the repository at this point in the history
* Implement the traversal subcommands for the H3 cli

* Move CLI tests to their own directory, splitting tests per subcommand

* Fix issue in gridDisk serialization pointed out by @isaacbrodsky

* Implement descriptions for H3Error enum values and use it in the CLI

* Address comments by @isaacbrodsky

* Add tests and docs for 'describeH3Error'

* Add more CLI tests and add missing newlines on some outputs

* Add testDescribeH3Error.c to a source file list

* Format testDescribeH3Error.c

* Address comments by @nrabinowitz

* Update website/docs/api/misc.mdx

Co-authored-by: Isaac Brodsky <[email protected]>

* Add calloc failure handling

* Switch the calloc error reporting to stderr

---------

Co-authored-by: Isaac Brodsky <[email protected]>
  • Loading branch information
dfellis and isaacbrodsky committed May 24, 2024
1 parent 77b6215 commit c18cd89
Show file tree
Hide file tree
Showing 26 changed files with 619 additions and 78 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ set(OTHER_SOURCE_FILES
src/apps/testapps/testH3Memory.c
src/apps/testapps/testH3IteratorsInternal.c
src/apps/testapps/testMathExtensionsInternal.c
src/apps/testapps/testDescribeH3Error.c
src/apps/miscapps/cellToBoundaryHier.c
src/apps/miscapps/cellToLatLngHier.c
src/apps/miscapps/generateBaseCellNeighbors.c
Expand Down
17 changes: 5 additions & 12 deletions CMakeTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ add_h3_test(testBaseCellsInternal src/apps/testapps/testBaseCellsInternal.c)
add_h3_test(testPentagonIndexes src/apps/testapps/testPentagonIndexes.c)
add_h3_test(testH3IteratorsInternal src/apps/testapps/testH3IteratorsInternal.c)
add_h3_test(testMathExtensionsInternal src/apps/testapps/testMathExtensionsInternal.c)
add_h3_test(testDescribeH3Error src/apps/testapps/testDescribeH3Error.c)

add_h3_test_with_arg(testH3NeighborRotations src/apps/testapps/testH3NeighborRotations.c 0)
add_h3_test_with_arg(testH3NeighborRotations src/apps/testapps/testH3NeighborRotations.c 1)
Expand All @@ -228,18 +229,10 @@ add_h3_test(testGridDistanceExhaustive src/apps/testapps/testGridDistanceExhaust
add_h3_test(testH3CellAreaExhaustive src/apps/testapps/testH3CellAreaExhaustive.c)
add_h3_test(testCellToBBoxExhaustive src/apps/testapps/testCellToBBoxExhaustive.c)

add_h3_cli_test(testCliCellToLatLng "cellToLatLng -c 8928342e20fffff" "POINT(-122.5003039349 37.5012466151)")
add_h3_cli_test(testCliLatLngToCell "latLngToCell --lat 20 --lng 123 -r 2" "824b9ffffffffff")
add_h3_cli_test(testCliCellToBoundary "cellToBoundary -c 8928342e20fffff" "POLYGON((-122.4990471431 37.4997389893, -122.4979805011 37.5014245698, -122.4992373065 37.5029321860, -122.5015607527 37.5027541980, -122.5026273256 37.5010686174, -122.5013705214 37.4995610248, -122.4990471431 37.4997389893))")
add_h3_cli_test(testCliGetResolution "getResolution -c 85283473fffffff" "5")
add_h3_cli_test(testCliGetBaseCellNumber "getBaseCellNumber -c 85283473fffffff" "20")
add_h3_cli_test(testCliStringToInt "stringToInt -c 85283473fffffff" "599686042433355775")
add_h3_cli_test(testCliIntToString "intToString -c 599686042433355775" "85283473fffffff")
add_h3_cli_test(testCliIsValidCell "isValidCell -c 85283473fffffff" "true")
add_h3_cli_test(testCliIsNotValidCell "isValidCell -c 85283473ffff" "false")
add_h3_cli_test(testCliIsResClassIII "isResClassIII -c 85283473fffffff" "true")
add_h3_cli_test(testCliIsPentagon "isPentagon -c 85283473fffffff" "false")
add_h3_cli_test(testCliGetIcosahedronFaces "getIcosahedronFaces -c 81743ffffffffff" "3, 8, 13, 9, 4")
file(GLOB cli_tests tests/cli/*.txt)
foreach(file ${cli_tests})
include(${file})
endforeach()

if(BUILD_ALLOC_TESTS)
add_h3_library(h3WithTestAllocators test_prefix_)
Expand Down
444 changes: 416 additions & 28 deletions src/apps/filters/h3.c

Large diffs are not rendered by default.

75 changes: 38 additions & 37 deletions src/apps/fuzzers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,56 +13,57 @@ The public API of H3 is covered in the following fuzzers:

| Function | File or status
| -------- | --------------
| latLngToCell | [fuzzerLatLngToCell](./fuzzerLatLngToCell.c)
| cellToLatLng | [fuzzerCellToLatLng](./fuzzerCellToLatLng.c)
| areNeighborCells | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| cellArea | [fuzzerCellArea](./fuzzerCellArea.c)
| cellToBoundary | [fuzzerCellToLatLng](./fuzzerCellToLatLng.c)
| gridDisk | [fuzzerGridDisk](./fuzzerGridDisk.c)
| gridDiskDistances | [fuzzerGridDisk](./fuzzerGridDisk.c)
| gridRingUnsafe | [fuzzerGridDisk](./fuzzerGridDisk.c)
| gridDisksUnsafe | [fuzzerGridDisk](./fuzzerGridDisk.c)
| polygonToCells | [fuzzerPoylgonToCells](./fuzzerPolygonToCells.c)
| h3SetToMultiPolygon | [fuzzerH3SetToLinkedGeo](./fuzzerH3SetToLinkedGeo.c)
| cellToCenterChild | [fuzzerHierarchy](./fuzzerHierarchy.c)
| cellToChildPos| [fuzzerCellToChildPos](./fuzzerCellToChildPos.c)
| cellToChildren | [fuzzerHierarchy](./fuzzerHierarchy.c)
| cellToLatLng | [fuzzerCellToLatLng](./fuzzerCellToLatLng.c)
| cellToLocalIj | [fuzzerLocalIj](./fuzzerLocalIj.c)
| cellToParent | [fuzzerHierarchy](./fuzzerHierarchy.c)
| cellToVertex | [fuzzerVertexes](./fuzzerVertexes.c)
| cellToVertexes | [fuzzerVertexes](./fuzzerVertexes.c)
| cellsToDirectedEdge | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| childPosToCell| [fuzzerCellToChildPos](./fuzzerCellToChildPos.c)
| compactCells | [fuzzerCompact](./fuzzerCompact.c)
| degsToRads | Trivial
| radsToDegs | Trivial
| describeH3Error | Trivial
| directedEdgeToBoundary | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| directedEdgeToCells | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| distance | [fuzzerDistances](./fuzzerDistances.c)
| edgeLength | [fuzzerEdgeLength](./fuzzerEdgeLength.c)
| getBaseCellNumber | [fuzzerCellProperties](./fuzzerCellProperties.c)
| getDirectedEdgeDestination | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| getDirectedEdgeOrigin | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| getHexagonAreaAvg | [fuzzerResolutions](./fuzzerResolutions.c)
| cellArea | [fuzzerCellArea](./fuzzerCellArea.c)
| getHexagonEdgeLengthAvg | [fuzzerResolutions](./fuzzerResolutions.c)
| edgeLength | [fuzzerEdgeLength](./fuzzerEdgeLength.c)
| getIcosahedronFaces | [fuzzerCellProperties](./fuzzerCellProperties.c)
| getNumCells | [fuzzerResolutions](./fuzzerResolutions.c)
| getRes0Cells | Trivial
| getPentagons | [fuzzerResolutions](./fuzzerResolutions.c)
| getRes0Cells | Trivial
| getResolution | [fuzzerCellProperties](./fuzzerCellProperties.c)
| getBaseCellNumber | [fuzzerCellProperties](./fuzzerCellProperties.c)
| stringToH3 | [fuzzerIndexIO](./fuzzerIndexIO.c)
| gridDisk | [fuzzerGridDisk](./fuzzerGridDisk.c)
| gridDiskDistances | [fuzzerGridDisk](./fuzzerGridDisk.c)
| gridDisksUnsafe | [fuzzerGridDisk](./fuzzerGridDisk.c)
| gridDistance | [fuzzerLocalIj](./fuzzerLocalIj.c)
| gridPathCells | [fuzzerLocalIj](./fuzzerLocalIj.c)
| gridRingUnsafe | [fuzzerGridDisk](./fuzzerGridDisk.c)
| h3SetToMultiPolygon | [fuzzerH3SetToLinkedGeo](./fuzzerH3SetToLinkedGeo.c)
| h3ToString | [fuzzerIndexIO](./fuzzerIndexIO.c)
| isValidCell | [fuzzerCellProperties](./fuzzerCellProperties.c)
| cellToParent | [fuzzerHierarchy](./fuzzerHierarchy.c)
| cellToChildren | [fuzzerHierarchy](./fuzzerHierarchy.c)
| cellToCenterChild | [fuzzerHierarchy](./fuzzerHierarchy.c)
| compactCells | [fuzzerCompact](./fuzzerCompact.c)
| uncompactCells | [fuzzerCompact](./fuzzerCompact.c)
| isResClassIII | [fuzzerCellProperties](./fuzzerCellProperties.c)
| isPentagon | [fuzzerCellProperties](./fuzzerCellProperties.c)
| getIcosahedronFaces | [fuzzerCellProperties](./fuzzerCellProperties.c)
| areNeighborCells | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| cellsToDirectedEdge | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| isResClassIII | [fuzzerCellProperties](./fuzzerCellProperties.c)
| isValidCell | [fuzzerCellProperties](./fuzzerCellProperties.c)
| isValidDirectedEdge | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| getDirectedEdgeOrigin | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| getDirectedEdgeDestination | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| directedEdgeToCells | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| originToDirectedEdges | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| directedEdgeToBoundary | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| cellToVertex | [fuzzerVertexes](./fuzzerVertexes.c)
| cellToVertexes | [fuzzerVertexes](./fuzzerVertexes.c)
| vertexToLatLng | [fuzzerVertexes](./fuzzerVertexes.c)
| isValidVertex | [fuzzerVertexes](./fuzzerVertexes.c)
| gridDistance | [fuzzerLocalIj](./fuzzerLocalIj.c)
| gridPathCells | [fuzzerLocalIj](./fuzzerLocalIj.c)
| cellToLocalIj | [fuzzerLocalIj](./fuzzerLocalIj.c)
| latLngToCell | [fuzzerLatLngToCell](./fuzzerLatLngToCell.c)
| localIjToCell | [fuzzerLocalIj](./fuzzerLocalIj.c)
| cellToChildPos| [fuzzerCellToChildPos](./fuzzerCellToChildPos.c)
| childPosToCell| [fuzzerCellToChildPos](./fuzzerCellToChildPos.c)
| originToDirectedEdges | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| polygonToCells | [fuzzerPoylgonToCells](./fuzzerPolygonToCells.c)
| radsToDegs | Trivial
| stringToH3 | [fuzzerIndexIO](./fuzzerIndexIO.c)
| uncompactCells | [fuzzerCompact](./fuzzerCompact.c)
| vertexToLatLng | [fuzzerVertexes](./fuzzerVertexes.c)

## Internal function coverage

Expand Down
51 changes: 51 additions & 0 deletions src/apps/testapps/testDescribeH3Error.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.
*/
/** @file
* @brief tests H3 function `describeH3Error`
*
* usage: `testDescribeH3Error`
*
* This program confirms that the `describeH3Error` function will provide
* a string output describing the error code (either providing a description
* of the error, or telling you that the error code is itself in error)
*/

#include <string.h>

#include "h3Index.h"
#include "test.h"

SUITE(describeH3Error) {
TEST(noError) {
H3Error err = E_SUCCESS;
t_assert(strcmp(H3_EXPORT(describeH3Error)(err), "Success") == 0,
"got expected success message");
}

TEST(invalidCell) {
H3Error err = E_CELL_INVALID;
t_assert(strcmp(H3_EXPORT(describeH3Error)(err),
"Cell argument was not valid") == 0,
"got expected error message");
}

TEST(invalidH3Error) {
H3Error err = 9001; // Will probably never hit this
t_assert(
strcmp(H3_EXPORT(describeH3Error)(err), "Invalid error code") == 0,
"got expected failure message");
}
}
8 changes: 8 additions & 0 deletions src/h3lib/include/h3api.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ typedef enum {
E_OPTION_INVALID = 15 // Mode or flags argument was not valid.
} H3ErrorCodes;

/** @defgroup describeH3Error describeH3Error
* Functions for describeH3Error
* @{
*/
/** @brief converts the provided H3Error value into a description string */
DECLSPEC const char *H3_EXPORT(describeH3Error)(H3Error err);
/** @} */

/* library version numbers generated from VERSION file */
// clang-format off
#define H3_VERSION_MAJOR @H3_VERSION_MAJOR@
Expand Down
39 changes: 38 additions & 1 deletion src/h3lib/lib/h3Index.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 Uber Technologies, Inc.
* Copyright 2016-2021, 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.
Expand Down Expand Up @@ -31,6 +31,43 @@
#include "iterators.h"
#include "mathExtensions.h"

/** @var H3ErrorDescriptions
* @brief An array of strings describing each of the H3ErrorCodes enum values
*/
static char *H3ErrorDescriptions[] = {
/* E_SUCCESS */ "Success",
/* E_FAILED */
"The operation failed but a more specific error is not available",
/* E_DOMAIN */ "Argument was outside of acceptable range",
/* E_LATLNG_DOMAIN */
"Latitude or longitude arguments were outside of acceptable range",
/* E_RES_DOMAIN */ "Resolution argument was outside of acceptable range",
/* E_CELL_INVALID */ "Cell argument was not valid",
/* E_DIR_EDGE_INVALID */ "Directed edge argument was not valid",
/* E_UNDIR_EDGE_INVALID */ "Undirected edge argument was not valid",
/* E_VERTEX_INVALID */ "Vertex argument was not valid",
/* E_PENTAGON */ "Pentagon distortion was encountered",
/* E_DUPLICATE_INPUT */ "Duplicate input",
/* E_NOT_NEIGHBORS */ "Cell arguments were not neighbors",
/* E_RES_MISMATCH */ "Cell arguments had incompatible resolutions",
/* E_MEMORY_ALLOC */ "Memory allocation failed",
/* E_MEMORY_BOUNDS */ "Bounds of provided memory were insufficient",
/* E_OPTION_INVALID */ "Mode or flags argument was not valid"};

/**
* Returns the string describing the H3Error. This string is internally
* allocated and should not be `free`d.
* @param err The H3 error.
* @return The string describing the H3Error
*/
const char *H3_EXPORT(describeH3Error)(H3Error err) {
if (err >= 0 && err <= 15) { // TODO: Better way to bounds check here?
return H3ErrorDescriptions[err];
} else {
return "Invalid error code";
}
}

/**
* Returns the H3 resolution of an H3 index.
* @param h The H3 index.
Expand Down
1 change: 1 addition & 0 deletions tests/cli/cellToBoundary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliCellToBoundary "cellToBoundary -c 8928342e20fffff" "POLYGON((-122.4990471431 37.4997389893, -122.4979805011 37.5014245698, -122.4992373065 37.5029321860, -122.5015607527 37.5027541980, -122.5026273256 37.5010686174, -122.5013705214 37.4995610248, -122.4990471431 37.4997389893))")
2 changes: 2 additions & 0 deletions tests/cli/cellToLatLng.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_h3_cli_test(testCliCellToLatLng "cellToLatLng -c 8928342e20fffff" "POINT(-122.5003039349 37.5012466151)")
add_h3_cli_test(testCliInvalidCellToLatLng "cellToLatLng -c asdf 2>&1" "Error 5: Cell argument was not valid")
1 change: 1 addition & 0 deletions tests/cli/cellToLocalIj.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliCellToLocalIj "cellToLocalIj -o 85283473fffffff -c 8528342bfffffff" "[25, 13]")
1 change: 1 addition & 0 deletions tests/cli/getBaseCellNumber.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliGetBaseCellNumber "getBaseCellNumber -c 85283473fffffff" "20")
1 change: 1 addition & 0 deletions tests/cli/getIcosahedronFaces.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliGetIcosahedronFaces "getIcosahedronFaces -c 81743ffffffffff" "3, 8, 13, 9, 4")
1 change: 1 addition & 0 deletions tests/cli/getResolution.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliGetResolution "getResolution -c 85283473fffffff" "5")
1 change: 1 addition & 0 deletions tests/cli/gridDisk.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliGridDisk "gridDisk -c 85283473fffffff -k 1" "[ \"85283473fffffff\", \"85283447fffffff\", \"8528347bfffffff\", \"85283463fffffff\", \"85283477fffffff\", \"8528340ffffffff\", \"8528340bfffffff\" ]")
1 change: 1 addition & 0 deletions tests/cli/gridDiskDistances.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliGridDiskDistances "gridDiskDistances -c 85283473fffffff -k 1" "[[\"85283473fffffff\"],[\"85283447fffffff\",\"8528347bfffffff\",\"85283463fffffff\",\"85283477fffffff\",\"8528340ffffffff\",\"8528340bfffffff\"]]")
1 change: 1 addition & 0 deletions tests/cli/gridDistance.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliGridDistance "gridDistance -o 85283473fffffff -d 8528342bfffffff" "2")
1 change: 1 addition & 0 deletions tests/cli/gridPathCells.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliGridPathCells "gridPathCells -o 85283473fffffff -d 8528342bfffffff" "[ \"85283473fffffff\", \"85283477fffffff\", \"8528342bfffffff\" ]")
1 change: 1 addition & 0 deletions tests/cli/gridRing.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliGridRing "gridRing -c 85283473fffffff -k 1" "[ \"8528340bfffffff\", \"85283447fffffff\", \"8528347bfffffff\", \"85283463fffffff\", \"85283477fffffff\", \"8528340ffffffff\" ]")
1 change: 1 addition & 0 deletions tests/cli/intToString.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliIntToString "intToString -c 599686042433355775" "85283473fffffff")
1 change: 1 addition & 0 deletions tests/cli/isPentagon.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliIsPentagon "isPentagon -c 85283473fffffff" "false")
1 change: 1 addition & 0 deletions tests/cli/isResClassIII.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliIsResClassIII "isResClassIII -c 85283473fffffff" "true")
2 changes: 2 additions & 0 deletions tests/cli/isValidCell.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_h3_cli_test(testCliIsValidCell "isValidCell -c 85283473fffffff" "true")
add_h3_cli_test(testCliIsNotValidCell "isValidCell -c 85283473ffff" "false")
1 change: 1 addition & 0 deletions tests/cli/latLngToCell.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliLatLngToCell "latLngToCell --lat 20 --lng 123 -r 2" "824b9ffffffffff")
1 change: 1 addition & 0 deletions tests/cli/localIjToCell.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliLocalIjToCell "localIjToCell -o 85283473fffffff -i 25 -j 13" "8528342bfffffff")
1 change: 1 addition & 0 deletions tests/cli/stringToInt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_h3_cli_test(testCliStringToInt "stringToInt -c 85283473fffffff" "599686042433355775")
42 changes: 42 additions & 0 deletions website/docs/api/misc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1057,3 +1057,45 @@ function example() {

Gives the "great circle" or "haversine" distance between pairs of
LatLng points (lat/lng pairs) in radians.

## describeH3Error

<Tabs
groupId="language"
defaultValue="c"
values={[
{label: 'C', value: 'c'},
{label: 'JavaScript (Live)', value: 'javascript'},
]
}>
<TabItem value="c">

```c
char *describeH3Error(H3Error err);
```
</TabItem>
<TabItem value="javascript">
:::note
Just read the `.message` property from the caught error, instead.
:::
```js live
function example() {
let errorMessage = ""
try {
h3.cellToChildrenSize("asdf", 9);
} catch (e) {
errorMessage = e.message;
}
return errorMessage;
}
```

</TabItem>
</Tabs>

Provides a human-readable description of an H3Error error code. This function cannot fail, as it just returns a string stating that the H3Error value is itself invalid and does not allocate memory to do so. Do not call `free` on the result of this function.

0 comments on commit c18cd89

Please sign in to comment.