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.