From 77b6215629015d28d9b43b6f85cadb196fe86567 Mon Sep 17 00:00:00 2001 From: David Ellis Date: Fri, 10 May 2024 16:58:38 -0500 Subject: [PATCH 1/5] Implement all of the inspection subcommands for the h3 cli program (#837) * Implement all of the inspection subcommands for the h3 cli program * Use formatting specifier as described by @grim7reaper * Free memory. Too used to Rust * Add index validation logic to other subcommands for consistency as pointed out by @isaacbrodsky * Rename SUBCMD and simplify error return value --- CMakeTests.cmake | 9 ++ src/apps/filters/h3.c | 277 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 250 insertions(+), 36 deletions(-) diff --git a/CMakeTests.cmake b/CMakeTests.cmake index a84f1b0d4..5d3934684 100644 --- a/CMakeTests.cmake +++ b/CMakeTests.cmake @@ -231,6 +231,15 @@ add_h3_test(testCellToBBoxExhaustive src/apps/testapps/testCellToBBoxExhaustive. 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") if(BUILD_ALLOC_TESTS) add_h3_library(h3WithTestAllocators test_prefix_) diff --git a/src/apps/filters/h3.c b/src/apps/filters/h3.c index db22fd57d..f527a43b8 100644 --- a/src/apps/filters/h3.c +++ b/src/apps/filters/h3.c @@ -21,6 +21,8 @@ #include +#include "h3api.h" + #ifdef _WIN32 #define strcasecmp _stricmp @@ -41,25 +43,66 @@ return E_SUCCESS; \ } +#define SUBCOMMAND(name, help) \ + Arg name##Arg = {.names = {#name}, .helpText = help}; \ + H3Error name##Cmd(int argc, char *argv[]) + +struct Subcommand { + char *name; + Arg *arg; + H3Error (*subcommand)(int, char **); +}; + +#define SUBCOMMANDS_INDEX \ + H3Error generalHelp(int argc, char *argv[]); \ + struct Subcommand subcommands[] = { +#define SUBCOMMAND_INDEX(s) {.name = #s, .arg = &s##Arg, .subcommand = &s##Cmd}, + +#define END_SUBCOMMANDS_INDEX \ + {.name = "--help", .arg = &helpArg, .subcommand = generalHelp}, { \ + .name = "-h", .arg = &helpArg, .subcommand = generalHelp \ + } \ + } \ + ; \ + \ + H3Error generalHelp(int argc, char *argv[]) { \ + int arglen = sizeof(subcommands) / sizeof(subcommands[0]) - 1; \ + Arg **args = calloc(arglen, sizeof(Arg *)); \ + args[0] = &helpArg; \ + for (int i = 0; i < arglen - 1; i++) { \ + args[i + 1] = subcommands[i].arg; \ + } \ + \ + const char *helpText = \ + "Please use one of the subcommands listed to perform an H3 " \ + "calculation. Use h3 --help for details on the " \ + "usage of " \ + "any subcommand."; \ + if (parseArgs(argc, argv, arglen, args, &helpArg, helpText)) { \ + free(args); \ + return E_SUCCESS; \ + } else { \ + free(args); \ + return E_FAILED; \ + } \ + } + +#define DISPATCH_SUBCOMMAND() \ + for (int i = 0; i < sizeof(subcommands) / sizeof(subcommands[0]); i++) { \ + if (has(subcommands[i].name, 1, argv)) { \ + return subcommands[i].subcommand(argc, argv); \ + } \ + } + bool has(char *subcommand, int level, char *argv[]) { return strcasecmp(subcommand, argv[level]) == 0; } Arg helpArg = ARG_HELP; -Arg cellToLatLngArg = { - .names = {"cellToLatLng"}, - .helpText = "Convert an H3 cell to a WKT POINT coordinate", -}; -Arg latLngToCellArg = { - .names = {"latLngToCell"}, - .helpText = "Convert degrees latitude/longitude coordinate to an H3 cell.", -}; -Arg cellToBoundaryArg = { - .names = {"cellToBoundary"}, - .helpText = "Convert an H3 cell to a WKT POLYGON defining its boundary", -}; -H3Error cellToLatLngCmd(int argc, char *argv[]) { +/// Indexing subcommands + +SUBCOMMAND(cellToLatLng, "Convert an H3Cell to a WKT POINT coordinate") { DEFINE_CELL_ARG(cell, cellArg); Arg *args[] = {&cellToLatLngArg, &helpArg, &cellArg}; PARSE_SUBCOMMAND(argc, argv, args); @@ -75,7 +118,8 @@ H3Error cellToLatLngCmd(int argc, char *argv[]) { return E_SUCCESS; } -H3Error latLngToCellCmd(int argc, char *argv[]) { +SUBCOMMAND(latLngToCell, + "Convert degrees latitude/longitude coordinate to an H3 cell") { int res = 0; double lat = 0; double lng = 0; @@ -117,7 +161,8 @@ H3Error latLngToCellCmd(int argc, char *argv[]) { return e; } -H3Error cellToBoundaryCmd(int argc, char *argv[]) { +SUBCOMMAND(cellToBoundary, + "Convert an H3 cell to a WKT POLYGON defining its boundary") { DEFINE_CELL_ARG(cell, cellArg); Arg *args[] = {&cellToBoundaryArg, &helpArg, &cellArg}; PARSE_SUBCOMMAND(argc, argv, args); @@ -140,35 +185,195 @@ H3Error cellToBoundaryCmd(int argc, char *argv[]) { return E_SUCCESS; } -bool generalHelp(int argc, char *argv[]) { - Arg *args[] = {&helpArg, &cellToLatLngArg, &latLngToCellArg, - &cellToBoundaryArg}; +/// Inspection subcommands - const char *helpText = - "Please use one of the subcommands listed to perform an H3 " - "calculation. Use h3 --help for details on the usage of " - "any subcommand."; - return parseArgs(argc, argv, sizeof(args) / sizeof(Arg *), args, &helpArg, - helpText); +SUBCOMMAND(getResolution, "Extracts the resolution (0 - 15) from the H3 cell") { + DEFINE_CELL_ARG(cell, cellArg); + Arg *args[] = {&getResolutionArg, &helpArg, &cellArg}; + PARSE_SUBCOMMAND(argc, argv, args); + // TODO: Should there be a general `isValidIndex`? + H3Error cellErr = H3_EXPORT(isValidCell)(cell); + H3Error edgeErr = H3_EXPORT(isValidDirectedEdge)(cell); + H3Error vertErr = H3_EXPORT(isValidVertex)(cell); + if (cellErr && edgeErr && vertErr) { + return cellErr; + } + // If we got here, we can use `getResolution` safely, as this is one of the + // few functions that doesn't do any error handling (for some reason? I + // don't see how this would ever be in a hot loop anywhere. + int res = H3_EXPORT(getResolution)(cell); + printf("%i", res); + return E_SUCCESS; } -int main(int argc, char *argv[]) { - if (argc <= 1) { - printf("Please use h3 --help to see how to use this command.\n"); - return 1; +SUBCOMMAND(getBaseCellNumber, + "Extracts the base cell number (0 - 121) from the H3 cell") { + DEFINE_CELL_ARG(cell, cellArg); + Arg *args[] = {&getBaseCellNumberArg, &helpArg, &cellArg}; + PARSE_SUBCOMMAND(argc, argv, args); + // TODO: Should there be a general `isValidIndex`? + H3Error cellErr = H3_EXPORT(isValidCell)(cell); + H3Error edgeErr = H3_EXPORT(isValidDirectedEdge)(cell); + H3Error vertErr = H3_EXPORT(isValidVertex)(cell); + if (cellErr && edgeErr && vertErr) { + return cellErr; } - if (has("cellToLatLng", 1, argv)) { - return cellToLatLngCmd(argc, argv); + // If we got here, we can use `getResolution` safely, as this is one of the + // few functions that doesn't do any error handling (for some reason? I + // don't see how this would ever be in a hot loop anywhere. + int baseCell = H3_EXPORT(getBaseCellNumber)(cell); + printf("%i", baseCell); + return E_SUCCESS; +} + +SUBCOMMAND(stringToInt, "Converts an H3 index in string form to integer form") { + char *rawCell = calloc(16, sizeof(char)); + Arg rawCellArg = {.names = {"-c", "--cell"}, + .required = true, + .scanFormat = "%s", + .valueName = "cell", + .value = rawCell, + .helpText = "H3 Cell Index"}; + Arg *args[] = {&stringToIntArg, &helpArg, &rawCellArg}; + PARSE_SUBCOMMAND(argc, argv, args); + H3Index c; + H3Error err = H3_EXPORT(stringToH3)(rawCell, &c); + if (err) { + free(rawCell); + return err; } - if (has("latLngToCell", 1, argv)) { - return latLngToCellCmd(argc, argv); + printf("%" PRIu64, c); + free(rawCell); + return E_SUCCESS; +} + +SUBCOMMAND(intToString, "Converts an H3 index in int form to string form") { + H3Index rawCell; + Arg rawCellArg = {.names = {"-c", "--cell"}, + .required = true, + .scanFormat = "%" PRIu64, + .valueName = "cell", + .value = &rawCell, + .helpText = "H3 Cell Index"}; + Arg *args[] = {&intToStringArg, &helpArg, &rawCellArg}; + PARSE_SUBCOMMAND(argc, argv, args); + h3Println(rawCell); + return E_SUCCESS; +} + +SUBCOMMAND(isValidCell, "Checks if the provided H3 index is actually valid") { + DEFINE_CELL_ARG(cell, cellArg); + Arg *args[] = {&isValidCellArg, &helpArg, &cellArg}; + PARSE_SUBCOMMAND(argc, argv, args); + bool isValid = H3_EXPORT(isValidCell)(cell); + printf("%s", isValid ? "true" : "false"); + return E_SUCCESS; +} + +SUBCOMMAND(isResClassIII, + "Checks if the provided H3 index has a Class III orientation") { + DEFINE_CELL_ARG(cell, cellArg); + Arg *args[] = {&isResClassIIIArg, &helpArg, &cellArg}; + PARSE_SUBCOMMAND(argc, argv, args); + // TODO: Should there be a general `isValidIndex`? + H3Error cellErr = H3_EXPORT(isValidCell)(cell); + H3Error edgeErr = H3_EXPORT(isValidDirectedEdge)(cell); + H3Error vertErr = H3_EXPORT(isValidVertex)(cell); + if (cellErr && edgeErr && vertErr) { + return cellErr; + } + // If we got here, we can use `getResolution` safely, as this is one of the + // few functions that doesn't do any error handling (for some reason? I + // don't see how this would ever be in a hot loop anywhere. + bool isClassIII = H3_EXPORT(isResClassIII)(cell); + printf("%s", isClassIII ? "true" : "false"); + return E_SUCCESS; +} + +SUBCOMMAND( + isPentagon, + "Checks if the provided H3 index is a pentagon instead of a hexagon") { + DEFINE_CELL_ARG(cell, cellArg); + Arg *args[] = {&isPentagonArg, &helpArg, &cellArg}; + PARSE_SUBCOMMAND(argc, argv, args); + // TODO: Should there be a general `isValidIndex`? + H3Error cellErr = H3_EXPORT(isValidCell)(cell); + H3Error edgeErr = H3_EXPORT(isValidDirectedEdge)(cell); + H3Error vertErr = H3_EXPORT(isValidVertex)(cell); + if (cellErr && edgeErr && vertErr) { + return cellErr; + } + // If we got here, we can use `getResolution` safely, as this is one of the + // few functions that doesn't do any error handling (for some reason? I + // don't see how this would ever be in a hot loop anywhere. + bool is = H3_EXPORT(isPentagon)(cell); + printf("%s", is ? "true" : "false"); + return E_SUCCESS; +} + +SUBCOMMAND(getIcosahedronFaces, + "Returns the icosahedron face numbers (0 - 19) that the H3 index " + "intersects") { + DEFINE_CELL_ARG(cell, cellArg); + Arg *args[] = {&getIcosahedronFacesArg, &helpArg, &cellArg}; + PARSE_SUBCOMMAND(argc, argv, args); + int faceCount; + H3Error err = H3_EXPORT(maxFaceCount)(cell, &faceCount); + if (err) { + return err; } - if (has("cellToBoundary", 1, argv)) { - return cellToBoundaryCmd(argc, argv); + int *faces = calloc(faceCount, sizeof(int)); + err = H3_EXPORT(getIcosahedronFaces)(cell, faces); + if (err) { + free(faces); + return err; } - if (generalHelp(argc, argv)) { - return 0; + bool hasPrinted = false; + for (int i = 0; i < faceCount - 1; i++) { + if (faces[i] != -1) { + if (hasPrinted) { + printf(", "); + } + printf("%i", faces[i]); + hasPrinted = true; + } + } + if (faces[faceCount - 1] != -1) { + if (hasPrinted) { + printf(", "); + } + printf("%i", faces[faceCount - 1]); + } + free(faces); + return E_SUCCESS; +} + +// TODO: Is there any way to avoid this particular piece of duplication? +SUBCOMMANDS_INDEX + +/// Indexing subcommands +SUBCOMMAND_INDEX(cellToLatLng) +SUBCOMMAND_INDEX(latLngToCell) +SUBCOMMAND_INDEX(cellToBoundary) + +/// Inspection subcommands +SUBCOMMAND_INDEX(getResolution) +SUBCOMMAND_INDEX(getBaseCellNumber) +SUBCOMMAND_INDEX(stringToInt) +SUBCOMMAND_INDEX(intToString) +SUBCOMMAND_INDEX(isValidCell) +SUBCOMMAND_INDEX(isResClassIII) +SUBCOMMAND_INDEX(isPentagon) +SUBCOMMAND_INDEX(getIcosahedronFaces) + +END_SUBCOMMANDS_INDEX + +int main(int argc, char *argv[]) { + if (argc <= 1) { + printf("Please use h3 --help to see how to use this command.\n"); + return 1; } + DISPATCH_SUBCOMMAND(); printf("Please use h3 --help to see how to use this command.\n"); return 1; } From c18cd89c153e9f149a3beb1db9c9a4d8d4e3cf27 Mon Sep 17 00:00:00 2001 From: David Ellis Date: Fri, 24 May 2024 14:11:04 -0500 Subject: [PATCH 2/5] Implement the traversal subcommands for the H3 cli (#839) * 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 * Add calloc failure handling * Switch the calloc error reporting to stderr --------- Co-authored-by: Isaac Brodsky --- CMakeLists.txt | 1 + CMakeTests.cmake | 17 +- src/apps/filters/h3.c | 444 ++++++++++++++++++++++-- src/apps/fuzzers/README.md | 75 ++-- src/apps/testapps/testDescribeH3Error.c | 51 +++ src/h3lib/include/h3api.h.in | 8 + src/h3lib/lib/h3Index.c | 39 ++- tests/cli/cellToBoundary.txt | 1 + tests/cli/cellToLatLng.txt | 2 + tests/cli/cellToLocalIj.txt | 1 + tests/cli/getBaseCellNumber.txt | 1 + tests/cli/getIcosahedronFaces.txt | 1 + tests/cli/getResolution.txt | 1 + tests/cli/gridDisk.txt | 1 + tests/cli/gridDiskDistances.txt | 1 + tests/cli/gridDistance.txt | 1 + tests/cli/gridPathCells.txt | 1 + tests/cli/gridRing.txt | 1 + tests/cli/intToString.txt | 1 + tests/cli/isPentagon.txt | 1 + tests/cli/isResClassIII.txt | 1 + tests/cli/isValidCell.txt | 2 + tests/cli/latLngToCell.txt | 1 + tests/cli/localIjToCell.txt | 1 + tests/cli/stringToInt.txt | 1 + website/docs/api/misc.mdx | 42 +++ 26 files changed, 619 insertions(+), 78 deletions(-) create mode 100644 src/apps/testapps/testDescribeH3Error.c create mode 100644 tests/cli/cellToBoundary.txt create mode 100644 tests/cli/cellToLatLng.txt create mode 100644 tests/cli/cellToLocalIj.txt create mode 100644 tests/cli/getBaseCellNumber.txt create mode 100644 tests/cli/getIcosahedronFaces.txt create mode 100644 tests/cli/getResolution.txt create mode 100644 tests/cli/gridDisk.txt create mode 100644 tests/cli/gridDiskDistances.txt create mode 100644 tests/cli/gridDistance.txt create mode 100644 tests/cli/gridPathCells.txt create mode 100644 tests/cli/gridRing.txt create mode 100644 tests/cli/intToString.txt create mode 100644 tests/cli/isPentagon.txt create mode 100644 tests/cli/isResClassIII.txt create mode 100644 tests/cli/isValidCell.txt create mode 100644 tests/cli/latLngToCell.txt create mode 100644 tests/cli/localIjToCell.txt create mode 100644 tests/cli/stringToInt.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index d86ee74ea..61724da9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/CMakeTests.cmake b/CMakeTests.cmake index 5d3934684..26261ec84 100644 --- a/CMakeTests.cmake +++ b/CMakeTests.cmake @@ -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) @@ -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_) diff --git a/src/apps/filters/h3.c b/src/apps/filters/h3.c index f527a43b8..e11c002b3 100644 --- a/src/apps/filters/h3.c +++ b/src/apps/filters/h3.c @@ -58,39 +58,48 @@ struct Subcommand { struct Subcommand subcommands[] = { #define SUBCOMMAND_INDEX(s) {.name = #s, .arg = &s##Arg, .subcommand = &s##Cmd}, -#define END_SUBCOMMANDS_INDEX \ - {.name = "--help", .arg = &helpArg, .subcommand = generalHelp}, { \ - .name = "-h", .arg = &helpArg, .subcommand = generalHelp \ - } \ - } \ - ; \ - \ - H3Error generalHelp(int argc, char *argv[]) { \ - int arglen = sizeof(subcommands) / sizeof(subcommands[0]) - 1; \ - Arg **args = calloc(arglen, sizeof(Arg *)); \ - args[0] = &helpArg; \ - for (int i = 0; i < arglen - 1; i++) { \ - args[i + 1] = subcommands[i].arg; \ - } \ - \ - const char *helpText = \ - "Please use one of the subcommands listed to perform an H3 " \ - "calculation. Use h3 --help for details on the " \ - "usage of " \ - "any subcommand."; \ - if (parseArgs(argc, argv, arglen, args, &helpArg, helpText)) { \ - free(args); \ - return E_SUCCESS; \ - } else { \ - free(args); \ - return E_FAILED; \ - } \ +#define END_SUBCOMMANDS_INDEX \ + {.name = "--help", .arg = &helpArg, .subcommand = generalHelp}, { \ + .name = "-h", .arg = &helpArg, .subcommand = generalHelp \ + } \ + } \ + ; \ + \ + H3Error generalHelp(int argc, char *argv[]) { \ + int arglen = sizeof(subcommands) / sizeof(subcommands[0]) - 1; \ + Arg **args = calloc(arglen, sizeof(Arg *)); \ + if (args == NULL) { \ + fprintf(stderr, "Failed to allocate memory for argument parsing"); \ + exit(1); \ + } \ + args[0] = &helpArg; \ + for (int i = 0; i < arglen - 1; i++) { \ + args[i + 1] = subcommands[i].arg; \ + } \ + \ + const char *helpText = \ + "Please use one of the subcommands listed to perform an H3 " \ + "calculation. Use h3 --help for details on the " \ + "usage of " \ + "any subcommand."; \ + if (parseArgs(argc, argv, arglen, args, &helpArg, helpText)) { \ + free(args); \ + return E_SUCCESS; \ + } else { \ + free(args); \ + return E_FAILED; \ + } \ } #define DISPATCH_SUBCOMMAND() \ for (int i = 0; i < sizeof(subcommands) / sizeof(subcommands[0]); i++) { \ if (has(subcommands[i].name, 1, argv)) { \ - return subcommands[i].subcommand(argc, argv); \ + H3Error err = subcommands[i].subcommand(argc, argv); \ + if (err != 0) { \ + fprintf(stderr, "Error %i: %s\n", err, \ + H3_EXPORT(describeH3Error)(err)); \ + } \ + return err; \ } \ } @@ -107,6 +116,10 @@ SUBCOMMAND(cellToLatLng, "Convert an H3Cell to a WKT POINT coordinate") { Arg *args[] = {&cellToLatLngArg, &helpArg, &cellArg}; PARSE_SUBCOMMAND(argc, argv, args); LatLng ll; + int valid = H3_EXPORT(isValidCell)(cell); + if (valid == 0) { + return E_CELL_INVALID; + } H3Error err = H3_EXPORT(cellToLatLng)(cell, &ll); if (err) { return err; @@ -167,6 +180,10 @@ SUBCOMMAND(cellToBoundary, Arg *args[] = {&cellToBoundaryArg, &helpArg, &cellArg}; PARSE_SUBCOMMAND(argc, argv, args); CellBoundary cb; + int valid = H3_EXPORT(isValidCell)(cell); + if (valid == 0) { + return E_CELL_INVALID; + } H3Error err = H3_EXPORT(cellToBoundary)(cell, &cb); if (err) { return err; @@ -228,6 +245,10 @@ SUBCOMMAND(getBaseCellNumber, SUBCOMMAND(stringToInt, "Converts an H3 index in string form to integer form") { char *rawCell = calloc(16, sizeof(char)); + if (rawCell == NULL) { + fprintf(stderr, "Failed to allocate memory for the H3 index"); + exit(1); + } Arg rawCellArg = {.names = {"-c", "--cell"}, .required = true, .scanFormat = "%s", @@ -323,6 +344,10 @@ SUBCOMMAND(getIcosahedronFaces, return err; } int *faces = calloc(faceCount, sizeof(int)); + if (faces == NULL) { + fprintf(stderr, "Failed to allocate memory for the icosahedron faces"); + exit(1); + } err = H3_EXPORT(getIcosahedronFaces)(cell, faces); if (err) { free(faces); @@ -348,6 +373,360 @@ SUBCOMMAND(getIcosahedronFaces, return E_SUCCESS; } +/// Traversal subcommands + +SUBCOMMAND( + gridDisk, + "Returns a JSON array of a H3 cells within 'k' steps of the origin cell") { + DEFINE_CELL_ARG(cell, cellArg); + int k = 0; + Arg kArg = {.names = {"-k"}, + .required = true, + .scanFormat = "%d", + .valueName = "distance", + .value = &k, + .helpText = "Maximum grid distance for the output set"}; + Arg *args[] = {&gridDiskArg, &helpArg, &cellArg, &kArg}; + PARSE_SUBCOMMAND(argc, argv, args); + int64_t len = 0; + H3Error err = H3_EXPORT(maxGridDiskSize)(k, &len); + if (err) { + return err; + } + H3Index *out = calloc(len, sizeof(H3Index)); + if (out == NULL) { + fprintf(stderr, "Failed to allocate memory for the output H3 cells"); + exit(1); + } + err = H3_EXPORT(gridDisk)(cell, k, out); + if (err) { + free(out); + return err; + } + // Since we don't know *actually* how many cells are in the output (usually + // the max, but sometimes not), we need to do a quick scan to figure out the + // true length in order to properly serialize to a JSON array + int64_t trueLen = 0; + for (int64_t i = 0; i < len; i++) { + if (out[i] != 0) { + trueLen++; + } + } + printf("[ "); + for (int64_t i = 0, j = 0; i < len; i++) { + if (out[i] != 0) { + j++; + printf("\"%" PRIx64 "\"%s", out[i], j == trueLen ? "" : ", "); + } + } + free(out); + printf(" ]\n"); + return E_SUCCESS; +} + +SUBCOMMAND( + gridDiskDistances, + "Returns a JSON array of arrays of H3 cells, each array containing cells " + "'k' steps away from the origin cell, based on the outer array index") { + DEFINE_CELL_ARG(cell, cellArg); + int k = 0; + Arg kArg = {.names = {"-k"}, + .required = true, + .scanFormat = "%d", + .valueName = "distance", + .value = &k, + .helpText = "Maximum grid distance for the output set"}; + Arg prettyArg = { + .names = {"-p", "--pretty-print"}, + .required = false, + .helpText = + "Determine if the JSON output should be pretty printed or not"}; + Arg *args[] = {&gridDiskDistancesArg, &helpArg, &cellArg, &kArg, + &prettyArg}; + PARSE_SUBCOMMAND(argc, argv, args); + bool pretty = prettyArg.found; + int64_t len = 0; + H3Error err = H3_EXPORT(maxGridDiskSize)(k, &len); + if (err) { + return err; + } + H3Index *out = calloc(len, sizeof(H3Index)); + if (out == NULL) { + fprintf(stderr, "Failed to allocate memory for the H3 cells"); + exit(1); + } + int *distances = calloc(len, sizeof(int)); + if (distances == NULL) { + fprintf(stderr, "Failed to allocate memory for the distances"); + exit(1); + } + err = H3_EXPORT(gridDiskDistances)(cell, k, out, distances); + if (err) { + free(out); + free(distances); + return err; + } + // Man, I wish JSON allowed trailing commas + printf("[%s", pretty ? "\n" : ""); + for (int i = 0; i <= k; i++) { + printf("%s[%s", /* prefix */ pretty ? " " : "", + /* suffix */ pretty ? "\n" : ""); + // We need to figure out how many cells are in each ring. Because of + // pentagons, we can't hardwire this, unfortunately + int count = 0; + for (int j = 0; j < len; j++) { + if (distances[j] == i && out[j] != 0) { + count++; + } + } + // On the second loop, we output cells with a comma except for the last + // one, which we now know + int cellNum = 0; + for (int j = 0; j < len; j++) { + if (distances[j] == i && out[j] != 0) { + cellNum++; + printf("%s\"%" PRIx64 "\"", pretty ? " " : "", out[j]); + if (cellNum == count) { + if (pretty) { + printf("\n"); + } + } else { + printf(",%s", pretty ? "\n" : ""); + } + } + } + // Similarly, we need to check which iteration of the outer array we're + // on and include a comma or not + if (i == k) { + printf("%s]%s", /* prefix */ pretty ? " " : "", + /* suffix */ pretty ? "\n" : ""); + } else { + printf("%s],%s", /* prefix */ pretty ? " " : "", + /* suffix */ pretty ? "\n" : ""); + } + } + printf("]\n"); // Always print the newline here so the terminal prompt gets + // its own line + free(out); + free(distances); + return E_SUCCESS; +} + +SUBCOMMAND(gridRing, + "Returns a JSON array of H3 cells, each cell 'k' steps away from " + "the origin cell") { + DEFINE_CELL_ARG(cell, cellArg); + int k = 0; + Arg kArg = {.names = {"-k"}, + .required = true, + .scanFormat = "%d", + .valueName = "distance", + .value = &k, + .helpText = "Maximum grid distance for the output set"}; + Arg *args[] = {&gridRingArg, &helpArg, &cellArg, &kArg}; + PARSE_SUBCOMMAND(argc, argv, args); + int64_t len = k == 0 ? 1 : 6 * k; // The length is fixed for gridRingUnsafe + // since it doesn't support pentagons + H3Index *out = calloc(len, sizeof(H3Index)); + if (out == NULL) { + fprintf(stderr, "Failed to allocate memory for the output H3 indexes"); + exit(1); + } + H3Error err = H3_EXPORT(gridRingUnsafe)(cell, k, out); + if (err) { + // For the CLI, we'll just do things less efficiently if there's an + // error here. If you use `gridDiskDistances` and only pay attention to + // the last array, it's equivalent to a "safe" gridRing call, but + // consumes a lot more temporary memory to do it + int64_t templen = 0; + err = H3_EXPORT(maxGridDiskSize)(k, &templen); + if (err) { + // But we abort if anything fails in here + free(out); + return err; + } + H3Index *temp = calloc(templen, sizeof(H3Index)); + if (temp == NULL) { + fprintf(stderr, + "Failed to allocate memory for a temporary hashset of H3 " + "indexes"); + exit(1); + } + int *distances = calloc(templen, sizeof(int)); + if (distances == NULL) { + fprintf(stderr, + "Failed to allocate memory for the distances of the H3 " + "indexes"); + exit(1); + } + err = H3_EXPORT(gridDiskDistances)(cell, k, temp, distances); + if (err) { + free(out); + free(temp); + free(distances); + return err; + } + // Now, we first re-zero the `out` array in case there's garbage + // anywhere in it from the failed computation. Then we scan through the + // gridDisk output and copy the indexes that are the correct distance + // in. We *should* only be in this path when there's a pentagon + // involved, so we expect the true length of the array to be less than + // what was allocated for `out` in this scenario. + for (int i = 0; i < len; i++) { + out[i] = 0; + } + int64_t count = 0; + for (int64_t i = 0; i < templen; i++) { + if (distances[i] == k && temp[i] != 0) { + out[count] = temp[i]; + count++; + } + } + len = count; + free(temp); + free(distances); + } + // Now that we have the correct data, however we got it, we can print it out + printf("[ \"%" PRIx64 "\"", out[0]); + for (int64_t i = 1; i < len; i++) { + if (out[i] != 0) { + printf(", \"%" PRIx64 "\"", out[i]); + } + } + free(out); + printf(" ]\n"); + return E_SUCCESS; +} + +SUBCOMMAND(gridPathCells, + "Returns a JSON array of H3 cells from the origin cell to the " + "destination cell (inclusive)") { + H3Index origin = 0; + Arg originArg = {.names = {"-o", "--origin"}, + .required = true, + .scanFormat = "%" PRIx64, + .valueName = "cell", + .value = &origin, + .helpText = "The origin H3 cell"}; + H3Index destination = 0; + Arg destinationArg = {.names = {"-d", "--destination"}, + .required = true, + .scanFormat = "%" PRIx64, + .valueName = "cell", + .value = &destination, + .helpText = "The destination H3 cell"}; + Arg *args[] = {&gridPathCellsArg, &helpArg, &originArg, &destinationArg}; + PARSE_SUBCOMMAND(argc, argv, args); + int64_t len = 0; + H3Error err = H3_EXPORT(gridPathCellsSize)(origin, destination, &len); + if (err) { + return err; + } + H3Index *out = calloc(len, sizeof(H3Index)); + if (out == NULL) { + fprintf(stderr, "Failed to allocate memory for the output H3 indexes"); + exit(1); + } + err = H3_EXPORT(gridPathCells)(origin, destination, out); + if (err) { + free(out); + return err; + } + printf("[ \"%" PRIx64 "\"", out[0]); + for (int64_t i = 1; i < len; i++) { + if (out[i] != 0) { + printf(", \"%" PRIx64 "\"", out[i]); + } + } + free(out); + printf(" ]\n"); + return E_SUCCESS; +} + +SUBCOMMAND(gridDistance, + "Returns the number of steps along the grid to move from the origin " + "cell to the destination cell") { + H3Index origin = 0; + Arg originArg = {.names = {"-o", "--origin"}, + .required = true, + .scanFormat = "%" PRIx64, + .valueName = "cell", + .value = &origin, + .helpText = "The origin H3 cell"}; + H3Index destination = 0; + Arg destinationArg = {.names = {"-d", "--destination"}, + .required = true, + .scanFormat = "%" PRIx64, + .valueName = "cell", + .value = &destination, + .helpText = "The destination H3 cell"}; + Arg *args[] = {&gridDistanceArg, &helpArg, &originArg, &destinationArg}; + PARSE_SUBCOMMAND(argc, argv, args); + int64_t distance = 0; + H3Error err = H3_EXPORT(gridDistance)(origin, destination, &distance); + if (err) { + return err; + } + printf("%" PRIx64 "\n", distance); + return E_SUCCESS; +} + +SUBCOMMAND(cellToLocalIj, + "Returns the IJ coordinate for a cell anchored to an origin cell") { + DEFINE_CELL_ARG(cell, cellArg); + H3Index origin = 0; + Arg originArg = {.names = {"-o", "--origin"}, + .required = true, + .scanFormat = "%" PRIx64, + .valueName = "cell", + .value = &origin, + .helpText = "The origin H3 cell"}; + Arg *args[] = {&cellToLocalIjArg, &helpArg, &cellArg, &originArg}; + PARSE_SUBCOMMAND(argc, argv, args); + CoordIJ out = {0}; + H3Error err = H3_EXPORT(cellToLocalIj)(origin, cell, 0, &out); + if (err) { + return err; + } + printf("[%i, %i]\n", out.i, out.j); + return E_SUCCESS; +} + +SUBCOMMAND(localIjToCell, + "Returns the H3 index from a local IJ coordinate anchored to an " + "origin cell") { + H3Index origin = 0; + Arg originArg = {.names = {"-o", "--origin"}, + .required = true, + .scanFormat = "%" PRIx64, + .valueName = "cell", + .value = &origin, + .helpText = "The origin H3 cell"}; + int i, j; + Arg iArg = {.names = {"-i"}, + .required = true, + .scanFormat = "%d", + .valueName = "i", + .value = &i, + .helpText = "The I dimension of the IJ coordinate"}; + Arg jArg = {.names = {"-j"}, + .required = true, + .scanFormat = "%d", + .valueName = "j", + .value = &j, + .helpText = "The J dimension of the IJ coordinate"}; + Arg *args[] = {&localIjToCellArg, &helpArg, &originArg, &iArg, &jArg}; + PARSE_SUBCOMMAND(argc, argv, args); + CoordIJ in = {.i = i, .j = j}; + H3Index out = 0; + H3Error err = H3_EXPORT(localIjToCell)(origin, &in, 0, &out); + if (err) { + return err; + } + h3Println(out); + return E_SUCCESS; +} + // TODO: Is there any way to avoid this particular piece of duplication? SUBCOMMANDS_INDEX @@ -366,6 +745,15 @@ SUBCOMMAND_INDEX(isResClassIII) SUBCOMMAND_INDEX(isPentagon) SUBCOMMAND_INDEX(getIcosahedronFaces) +/// Traversal subcommands +SUBCOMMAND_INDEX(gridDisk) +SUBCOMMAND_INDEX(gridDiskDistances) +SUBCOMMAND_INDEX(gridRing) +SUBCOMMAND_INDEX(gridPathCells) +SUBCOMMAND_INDEX(gridDistance) +SUBCOMMAND_INDEX(cellToLocalIj) +SUBCOMMAND_INDEX(localIjToCell) + END_SUBCOMMANDS_INDEX int main(int argc, char *argv[]) { diff --git a/src/apps/fuzzers/README.md b/src/apps/fuzzers/README.md index 392c9c206..c7349e36f 100644 --- a/src/apps/fuzzers/README.md +++ b/src/apps/fuzzers/README.md @@ -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 diff --git a/src/apps/testapps/testDescribeH3Error.c b/src/apps/testapps/testDescribeH3Error.c new file mode 100644 index 000000000..d275c6abd --- /dev/null +++ b/src/apps/testapps/testDescribeH3Error.c @@ -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 + +#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"); + } +} diff --git a/src/h3lib/include/h3api.h.in b/src/h3lib/include/h3api.h.in index 58f6d9e84..5fdbeb41b 100644 --- a/src/h3lib/include/h3api.h.in +++ b/src/h3lib/include/h3api.h.in @@ -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@ diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index bccd4baf5..193622caf 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -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. @@ -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. diff --git a/tests/cli/cellToBoundary.txt b/tests/cli/cellToBoundary.txt new file mode 100644 index 000000000..43535d658 --- /dev/null +++ b/tests/cli/cellToBoundary.txt @@ -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))") diff --git a/tests/cli/cellToLatLng.txt b/tests/cli/cellToLatLng.txt new file mode 100644 index 000000000..1fb89fd98 --- /dev/null +++ b/tests/cli/cellToLatLng.txt @@ -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") diff --git a/tests/cli/cellToLocalIj.txt b/tests/cli/cellToLocalIj.txt new file mode 100644 index 000000000..ab3cb7b13 --- /dev/null +++ b/tests/cli/cellToLocalIj.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliCellToLocalIj "cellToLocalIj -o 85283473fffffff -c 8528342bfffffff" "[25, 13]") diff --git a/tests/cli/getBaseCellNumber.txt b/tests/cli/getBaseCellNumber.txt new file mode 100644 index 000000000..51a96dd3f --- /dev/null +++ b/tests/cli/getBaseCellNumber.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliGetBaseCellNumber "getBaseCellNumber -c 85283473fffffff" "20") diff --git a/tests/cli/getIcosahedronFaces.txt b/tests/cli/getIcosahedronFaces.txt new file mode 100644 index 000000000..2bba69731 --- /dev/null +++ b/tests/cli/getIcosahedronFaces.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliGetIcosahedronFaces "getIcosahedronFaces -c 81743ffffffffff" "3, 8, 13, 9, 4") diff --git a/tests/cli/getResolution.txt b/tests/cli/getResolution.txt new file mode 100644 index 000000000..120d16353 --- /dev/null +++ b/tests/cli/getResolution.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliGetResolution "getResolution -c 85283473fffffff" "5") diff --git a/tests/cli/gridDisk.txt b/tests/cli/gridDisk.txt new file mode 100644 index 000000000..7279ab489 --- /dev/null +++ b/tests/cli/gridDisk.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliGridDisk "gridDisk -c 85283473fffffff -k 1" "[ \"85283473fffffff\", \"85283447fffffff\", \"8528347bfffffff\", \"85283463fffffff\", \"85283477fffffff\", \"8528340ffffffff\", \"8528340bfffffff\" ]") \ No newline at end of file diff --git a/tests/cli/gridDiskDistances.txt b/tests/cli/gridDiskDistances.txt new file mode 100644 index 000000000..ed59e0d23 --- /dev/null +++ b/tests/cli/gridDiskDistances.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliGridDiskDistances "gridDiskDistances -c 85283473fffffff -k 1" "[[\"85283473fffffff\"],[\"85283447fffffff\",\"8528347bfffffff\",\"85283463fffffff\",\"85283477fffffff\",\"8528340ffffffff\",\"8528340bfffffff\"]]") diff --git a/tests/cli/gridDistance.txt b/tests/cli/gridDistance.txt new file mode 100644 index 000000000..422ddee27 --- /dev/null +++ b/tests/cli/gridDistance.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliGridDistance "gridDistance -o 85283473fffffff -d 8528342bfffffff" "2") diff --git a/tests/cli/gridPathCells.txt b/tests/cli/gridPathCells.txt new file mode 100644 index 000000000..a5b60d448 --- /dev/null +++ b/tests/cli/gridPathCells.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliGridPathCells "gridPathCells -o 85283473fffffff -d 8528342bfffffff" "[ \"85283473fffffff\", \"85283477fffffff\", \"8528342bfffffff\" ]") diff --git a/tests/cli/gridRing.txt b/tests/cli/gridRing.txt new file mode 100644 index 000000000..8fce2524b --- /dev/null +++ b/tests/cli/gridRing.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliGridRing "gridRing -c 85283473fffffff -k 1" "[ \"8528340bfffffff\", \"85283447fffffff\", \"8528347bfffffff\", \"85283463fffffff\", \"85283477fffffff\", \"8528340ffffffff\" ]") diff --git a/tests/cli/intToString.txt b/tests/cli/intToString.txt new file mode 100644 index 000000000..6a96d94f3 --- /dev/null +++ b/tests/cli/intToString.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliIntToString "intToString -c 599686042433355775" "85283473fffffff") diff --git a/tests/cli/isPentagon.txt b/tests/cli/isPentagon.txt new file mode 100644 index 000000000..9e86d888e --- /dev/null +++ b/tests/cli/isPentagon.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliIsPentagon "isPentagon -c 85283473fffffff" "false") diff --git a/tests/cli/isResClassIII.txt b/tests/cli/isResClassIII.txt new file mode 100644 index 000000000..ec31d3110 --- /dev/null +++ b/tests/cli/isResClassIII.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliIsResClassIII "isResClassIII -c 85283473fffffff" "true") diff --git a/tests/cli/isValidCell.txt b/tests/cli/isValidCell.txt new file mode 100644 index 000000000..5de8b85fd --- /dev/null +++ b/tests/cli/isValidCell.txt @@ -0,0 +1,2 @@ +add_h3_cli_test(testCliIsValidCell "isValidCell -c 85283473fffffff" "true") +add_h3_cli_test(testCliIsNotValidCell "isValidCell -c 85283473ffff" "false") diff --git a/tests/cli/latLngToCell.txt b/tests/cli/latLngToCell.txt new file mode 100644 index 000000000..0d682a3bb --- /dev/null +++ b/tests/cli/latLngToCell.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliLatLngToCell "latLngToCell --lat 20 --lng 123 -r 2" "824b9ffffffffff") diff --git a/tests/cli/localIjToCell.txt b/tests/cli/localIjToCell.txt new file mode 100644 index 000000000..3b69d4a2e --- /dev/null +++ b/tests/cli/localIjToCell.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliLocalIjToCell "localIjToCell -o 85283473fffffff -i 25 -j 13" "8528342bfffffff") diff --git a/tests/cli/stringToInt.txt b/tests/cli/stringToInt.txt new file mode 100644 index 000000000..d332f9e1a --- /dev/null +++ b/tests/cli/stringToInt.txt @@ -0,0 +1 @@ +add_h3_cli_test(testCliStringToInt "stringToInt -c 85283473fffffff" "599686042433355775") diff --git a/website/docs/api/misc.mdx b/website/docs/api/misc.mdx index d643f55a8..ffeba3cc9 100644 --- a/website/docs/api/misc.mdx +++ b/website/docs/api/misc.mdx @@ -1057,3 +1057,45 @@ function example() { Gives the "great circle" or "haversine" distance between pairs of LatLng points (lat/lng pairs) in radians. + +## describeH3Error + + + + +```c +char *describeH3Error(H3Error err); +``` + + + + +:::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; +} +``` + + + + +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. From efdd514b2d80c49973e5dd55a842f68be73ed0e5 Mon Sep 17 00:00:00 2001 From: David Ellis Date: Tue, 4 Jun 2024 18:25:04 -0500 Subject: [PATCH 3/5] Fix cellToChildPos and childPosToCell docs (#845) --- website/package.json | 2 +- website/yarn.lock | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/website/package.json b/website/package.json index 1cff5d1b3..6f7a5fc1f 100644 --- a/website/package.json +++ b/website/package.json @@ -20,7 +20,7 @@ "@docusaurus/theme-live-codeblock": "^2.0.1", "@mdx-js/react": "^1.6.22", "global": "^4.4.0", - "h3-js": "^4.0.0", + "h3-js": "4.1.0", "h3-jsv3": "npm:h3-js@^3.7.1", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/website/yarn.lock b/website/yarn.lock index 865201e78..8b0bd4489 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -1546,7 +1546,7 @@ "@docusaurus/theme-search-algolia" "2.0.1" "@docusaurus/types" "2.0.1" -"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": +"@docusaurus/react-loadable@5.5.2": version "5.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== @@ -4271,10 +4271,10 @@ gzip-size@^6.0.0: dependencies: duplexer "^0.1.2" -h3-js@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/h3-js/-/h3-js-4.0.0.tgz#f55efb89f4608014146f3839d1d01d7a2767b3ba" - integrity sha512-zIx1NIflzkQ6Jw0tzVOba1wkJvZ/uwVHnuOrgK6VGFzsWdM/QUC3h2ALk2RizyxSNAi0UibmftniyfVK3BVf7A== +h3-js@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/h3-js/-/h3-js-4.1.0.tgz#f8c4a8ad36612489a954f1a0bb3f4b7657d364e5" + integrity sha512-LQhmMl1dRQQjMXPzJc7MpZ/CqPOWWuAvVEoVJM9n/s7vHypj+c3Pd5rLQCkAsOgAoAYKbNCsYFE++LF7MvSfCQ== "h3-jsv3@npm:h3-js@^3.7.1": version "3.7.2" @@ -6263,6 +6263,14 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" +"react-loadable@npm:@docusaurus/react-loadable@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" + integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== + dependencies: + "@types/react" "*" + prop-types "^15.6.2" + react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" From 9ae127f21ede3a0cce3eb3f91c11b6401fb2584e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:12:01 -0700 Subject: [PATCH 4/5] Bump braces from 3.0.2 to 3.0.3 in /website (#847) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- website/yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 8b0bd4489..dd3cbc34b 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -2770,11 +2770,11 @@ brace-expansion@^1.1.7: concat-map "0.0.1" braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.18.1, browserslist@^4.20.2, browserslist@^4.20.3, browserslist@^4.21.3: version "4.21.3" @@ -3961,10 +3961,10 @@ filesize@^8.0.6: resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" From ecc0d254d2fcc93a8df807b21e116f942092d161 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:44:18 -0700 Subject: [PATCH 5/5] Bump ws from 7.5.9 to 7.5.10 in /website (#848) Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- website/yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index dd3cbc34b..2d0081945 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -7819,14 +7819,14 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.3.1: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.4.2: - version "8.8.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0" - integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xdg-basedir@^4.0.0: version "4.0.0"