Skip to content

Commit

Permalink
Implement all of the inspection subcommands for the h3 cli program (#837
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
dfellis committed May 10, 2024
1 parent 3a71b09 commit 77b6215
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 36 deletions.
9 changes: 9 additions & 0 deletions CMakeTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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_)
Expand Down
277 changes: 241 additions & 36 deletions src/apps/filters/h3.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

#include <string.h>

#include "h3api.h"

#ifdef _WIN32

#define strcasecmp _stricmp
Expand All @@ -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 <SUBCOMMAND> --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);
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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 <SUBCOMMAND> --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;
}

0 comments on commit 77b6215

Please sign in to comment.