src/apps/filters/h3.c (2,683 lines of code) (raw):
/*
* Copyright 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.
* 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 cli app that exposes most of the H3 C library for scripting
*
* See `h3 --help` for usage.
*/
#include <string.h>
#include "h3api.h"
#ifdef _WIN32
#define strcasecmp _stricmp
#else
#include <strings.h>
#endif
#include "args.h"
#include "h3Index.h"
#include "utility.h"
#define BUFFER_SIZE 1500
#define BUFFER_SIZE_LESS_CELL 1485
#define BUFFER_SIZE_WITH_NULL 1501
#define PARSE_SUBCOMMAND(argc, argv, args) \
if (parseArgs(argc, argv, sizeof(args) / sizeof(Arg *), args, &helpArg, \
args[0]->helpText)) { \
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 *)); \
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 <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)) { \
H3Error err = subcommands[i].subcommand(argc, argv); \
if (err != 0) { \
fprintf(stderr, "Error %i: %s\n", err, \
H3_EXPORT(describeH3Error)(err)); \
} \
return err; \
} \
}
bool has(char *subcommand, int level, char *argv[]) {
return strcasecmp(subcommand, argv[level]) == 0;
}
Arg helpArg = ARG_HELP;
#define DEFINE_FORMAT_ARG(desc) \
char format[8] = {0}; \
Arg formatArg = {.names = {"-f", "--format"}, \
.scanFormat = "%7s", \
.value = format, \
.valueName = "FMT", \
.helpText = desc}
/// Indexing subcommands
SUBCOMMAND(cellToLatLng, "Convert an H3Cell to a coordinate") {
DEFINE_FORMAT_ARG(
"'json' for [lat, lng], 'wkt' for a WKT POINT, 'newline' for "
"lat\\nlng\\n (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&cellToLatLngArg, &helpArg, &cellArg, &formatArg};
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;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[%.10lf, %.10lf]\n", H3_EXPORT(radsToDegs)(ll.lat),
H3_EXPORT(radsToDegs)(ll.lng));
} else if (strcmp(format, "wkt") == 0) {
// Using WKT formatting for the output. TODO: Add support for JSON
// formatting
printf("POINT(%.10lf %.10lf)\n", H3_EXPORT(radsToDegs)(ll.lng),
H3_EXPORT(radsToDegs)(ll.lat));
} else if (strcmp(format, "newline") == 0) {
printf("%.10lf\n%.10lf\n", H3_EXPORT(radsToDegs)(ll.lat),
H3_EXPORT(radsToDegs)(ll.lng));
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(latLngToCell,
"Convert degrees latitude/longitude coordinate to an H3 cell") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n "
"(Default: json)");
int res = 0;
double lat = 0;
double lng = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText = "Resolution, 0-15 inclusive."};
Arg latArg = {.names = {"--lat", "--latitude"},
.required = true,
.scanFormat = "%lf",
.valueName = "lat",
.value = &lat,
.helpText = "Latitude in degrees."};
Arg lngArg = {.names = {"--lng", "--longitude"},
.required = true,
.scanFormat = "%lf",
.valueName = "lng",
.value = &lng,
.helpText = "Longitude in degrees."};
Arg *args[] = {&latLngToCellArg, &helpArg, &resArg,
&latArg, &lngArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
LatLng ll = {.lat = H3_EXPORT(degsToRads)(lat),
.lng = H3_EXPORT(degsToRads)(lng)};
H3Index c;
H3Error err = H3_EXPORT(latLngToCell)(&ll, res, &c);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", c);
} else if (strcmp(format, "newline") == 0) {
h3Println(c);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(cellToBoundary,
"Convert an H3 cell to a polygon defining its boundary") {
DEFINE_FORMAT_ARG(
"'json' for [[lat, lng], ...], 'wkt' for a WKT POLYGON, 'newline' for "
"lat\\nlng\\n... (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&cellToBoundaryArg, &helpArg, &cellArg, &formatArg};
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;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[");
for (int i = 0; i < cb.numVerts - 1; i++) {
LatLng *ll = &cb.verts[i];
printf("[%.10lf, %.10lf], ", H3_EXPORT(radsToDegs)(ll->lat),
H3_EXPORT(radsToDegs)(ll->lng));
}
printf("[%.10lf, %.10lf]]\n",
H3_EXPORT(radsToDegs)(cb.verts[cb.numVerts - 1].lat),
H3_EXPORT(radsToDegs)(cb.verts[cb.numVerts - 1].lng));
} else if (strcmp(format, "wkt") == 0) {
printf("POLYGON((");
for (int i = 0; i < cb.numVerts; i++) {
LatLng *ll = &cb.verts[i];
printf("%.10lf %.10lf, ", H3_EXPORT(radsToDegs)(ll->lng),
H3_EXPORT(radsToDegs)(ll->lat));
}
// WKT has the first and last points match, so re-print the first one
printf("%.10lf %.10lf))\n", H3_EXPORT(radsToDegs)(cb.verts[0].lng),
H3_EXPORT(radsToDegs)(cb.verts[0].lat));
} else if (strcmp(format, "newline") == 0) {
for (int i = 0; i < cb.numVerts; i++) {
LatLng *ll = &cb.verts[i];
printf("%.10lf\n%.10lf\n", H3_EXPORT(radsToDegs)(ll->lat),
H3_EXPORT(radsToDegs)(ll->lng));
}
} else {
return E_FAILED;
}
return E_SUCCESS;
}
/// Inspection subcommands
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\n", res);
return E_SUCCESS;
}
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 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\n", baseCell);
return E_SUCCESS;
}
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",
.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;
}
printf("%" PRIu64 "\n", 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_FORMAT_ARG(
"'json' for true or false, 'numeric' for 1 or 0 (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&isValidCellArg, &helpArg, &cellArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
bool isValid = H3_EXPORT(isValidCell)(cell);
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("%s\n", isValid ? "true" : "false");
} else if (strcmp(format, "numeric") == 0) {
printf("%d\n", isValid);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(isResClassIII,
"Checks if the provided H3 index has a Class III orientation") {
DEFINE_FORMAT_ARG(
"'json' for true or false, 'numeric' for 1 or 0 (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&isResClassIIIArg, &helpArg, &cellArg, &formatArg};
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);
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("%s\n", isClassIII ? "true" : "false");
} else if (strcmp(format, "numeric") == 0) {
printf("%d\n", isClassIII);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(
isPentagon,
"Checks if the provided H3 index is a pentagon instead of a hexagon") {
DEFINE_FORMAT_ARG(
"'json' for true or false, 'numeric' for 1 or 0 (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&isPentagonArg, &helpArg, &cellArg, &formatArg};
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);
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("%s\n", is ? "true" : "false");
} else if (strcmp(format, "numeric") == 0) {
printf("%d\n", is);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(getIcosahedronFaces,
"Returns the icosahedron face numbers (0 - 19) that the H3 index "
"intersects") {
DEFINE_FORMAT_ARG(
"'json' for [faceNum, ...], 'newline' for faceNum\\n... (Default: "
"json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&getIcosahedronFacesArg, &helpArg, &cellArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
int faceCount;
H3Error err = H3_EXPORT(maxFaceCount)(cell, &faceCount);
if (err) {
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);
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
bool hasPrinted = false;
printf("[");
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]);
}
printf("]\n");
} else if (strcmp(format, "newline") == 0) {
for (int i = 0; i < faceCount; i++) {
if (faces[i] != -1) {
printf("%i\n", faces[i]);
}
}
} else {
free(faces);
return E_FAILED;
}
free(faces);
return E_SUCCESS;
}
/// Traversal subcommands
SUBCOMMAND(
gridDisk,
"Returns an array of a H3 cells within 'k' steps of the origin cell") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
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, &formatArg};
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;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// 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 ? "" : ", ");
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < len; i++) {
if (out[i] != 0) {
h3Println(out[i]);
}
}
} else {
free(out);
return E_FAILED;
}
free(out);
return E_SUCCESS;
}
SUBCOMMAND(
gridDiskDistances,
"Returns an array of arrays of H3 cells, each array containing cells "
"'k' steps away from the origin cell, based on the outer array index") {
DEFINE_FORMAT_ARG(
"'json' for [[\"CELL\", ...], ...], 'newline' for CELL\\n with an "
"extra newline between rings (Default: json)");
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[] = {&gridDiskDistancesArg, &helpArg, &cellArg, &kArg,
&formatArg};
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 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;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// Man, I wish JSON allowed trailing commas
printf("[");
for (int i = 0; i <= k; i++) {
printf("[");
// 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("\"%" PRIx64 "\"", out[j]);
if (cellNum != count) {
printf(", ");
}
}
}
// Similarly, we need to check which iteration of the outer array
// we're on and include a comma or not
if (i == k) {
printf("]");
} else {
printf("], ");
}
}
printf("]\n"); // Always print the newline here so the terminal prompt
// gets its own line
} else if (strcmp(format, "newline") == 0) {
for (int i = 0; i <= k; i++) {
for (int j = 0; j < len; j++) {
if (distances[j] == i && out[j] != 0) {
h3Println(out[j]);
}
}
if (i < k) {
printf("\n");
}
}
} else {
free(out);
free(distances);
return E_FAILED;
}
free(out);
free(distances);
return E_SUCCESS;
}
SUBCOMMAND(gridRing,
"Returns an array of H3 cells, each cell 'k' steps away from "
"the origin cell") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
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, &formatArg};
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
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[ \"%" PRIx64 "\"", out[0]);
for (int64_t i = 1; i < len; i++) {
if (out[i] != 0) {
printf(", \"%" PRIx64 "\"", out[i]);
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < len; i++) {
h3Println(out[i]);
}
} else {
free(out);
return E_FAILED;
}
free(out);
return E_SUCCESS;
}
SUBCOMMAND(gridPathCells,
"Returns an array of H3 cells from the origin cell to the "
"destination cell (inclusive)") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
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,
&formatArg};
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;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[ \"%" PRIx64 "\"", out[0]);
for (int64_t i = 1; i < len; i++) {
if (out[i] != 0) {
printf(", \"%" PRIx64 "\"", out[i]);
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < len; i++) {
h3Println(out[i]);
}
} else {
free(out);
return E_FAILED;
}
free(out);
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_FORMAT_ARG(
"'json' for [I, J], 'newline' for I\\nJ\\n (Default: json)");
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,
&formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
CoordIJ out = {0};
H3Error err = H3_EXPORT(cellToLocalIj)(origin, cell, 0, &out);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[%i, %i]\n", out.i, out.j);
} else if (strcmp(format, "newline") == 0) {
printf("%i\n%i\n", out.i, out.j);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(localIjToCell,
"Returns the H3 index from a local IJ coordinate anchored to an "
"origin cell") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n (Default: json)");
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,
&formatArg};
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;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", out);
} else if (strcmp(format, "newline") == 0) {
h3Println(out);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
/// Hierarchical subcommands
SUBCOMMAND(cellToParent,
"Returns the H3 index that is the parent (or higher) of the "
"provided cell") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 0-14 inclusive, excluding 15 as it can "
"never be a parent"};
Arg *args[] = {&cellToParentArg, &helpArg, &cellArg, &resArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index parent;
int valid = H3_EXPORT(isValidCell)(cell);
if (valid == 0) {
return E_CELL_INVALID;
}
H3Error err = H3_EXPORT(cellToParent)(cell, res, &parent);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", parent);
} else if (strcmp(format, "newline") == 0) {
h3Println(parent);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(cellToChildren,
"Returns an array of H3 indexes that are the children (or "
"lower) of the provided cell") {
// TODO: This function contains a lot of code that is very similar to
// `gridDisk`. If this happens again we should DRY them.
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 1-15 inclusive, excluding 0 as it can "
"never be a child"};
Arg *args[] = {&cellToChildrenArg, &helpArg, &cellArg, &resArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
int64_t len = 0;
H3Error err = H3_EXPORT(cellToChildrenSize)(cell, res, &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(cellToChildren)(cell, res, out);
if (err) {
free(out);
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// 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 ? "" : ", ");
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < len; i++) {
if (out[i] != 0) {
h3Println(out[i]);
}
}
} else {
free(out);
return E_FAILED;
}
free(out);
return E_SUCCESS;
}
SUBCOMMAND(cellToChildrenSize,
"Returns the maximum number of children for a given cell at the "
"specified child resolution") {
// TODO: Do we want to include this subcommand or no? It can be useful to
// let someone decide for themselves if they really want to run the command
// and get a potentially massive number of cells as the output, but is that
// a concern a CLI user would have? They'd probably just ^C it.
DEFINE_CELL_ARG(cell, cellArg);
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 1-15 inclusive, excluding 0 as it can "
"never be a child"};
Arg *args[] = {&cellToChildrenSizeArg, &helpArg, &cellArg, &resArg};
PARSE_SUBCOMMAND(argc, argv, args);
int64_t len = 0;
H3Error err = H3_EXPORT(cellToChildrenSize)(cell, res, &len);
if (err) {
return err;
}
printf("%" PRId64 "\n", len);
return E_SUCCESS;
}
SUBCOMMAND(
cellToCenterChild,
"Returns the H3 index that is the centrally-placed child (or lower) of the "
"provided cell") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 1-15 inclusive, excluding 0 as it can "
"never be a child"};
Arg *args[] = {&cellToCenterChildArg, &helpArg, &cellArg, &resArg,
&formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index centerChild;
int valid = H3_EXPORT(isValidCell)(cell);
if (valid == 0) {
return E_CELL_INVALID;
}
H3Error err = H3_EXPORT(cellToCenterChild)(cell, res, ¢erChild);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", centerChild);
} else if (strcmp(format, "newline") == 0) {
h3Println(centerChild);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(
cellToChildPos,
"Returns the position of the child cell within an ordered list of all "
"children of the cell's parent at the specified child resolution") {
DEFINE_CELL_ARG(cell, cellArg);
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 1-15 inclusive, excluding 0 as it can "
"never be a child"};
Arg *args[] = {&cellToChildPosArg, &helpArg, &cellArg, &resArg};
PARSE_SUBCOMMAND(argc, argv, args);
int64_t len = 0;
H3Error err = H3_EXPORT(cellToChildPos)(cell, res, &len);
if (err) {
return err;
}
printf("%" PRId64 "\n", len);
return E_SUCCESS;
}
SUBCOMMAND(childPosToCell,
"Returns the child cell at a given position and resolution within "
"an ordered list of all children of the parent cell") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 1-15 inclusive, excluding 0 as it can "
"never be a child"};
int64_t pos;
Arg posArg = {
.names = {"-p", "--position"},
.required = true,
.scanFormat = "%" PRIi64,
.valueName = "pos",
.value = &pos,
.helpText =
"The child position within the set of children of the parent cell"};
Arg *args[] = {&childPosToCellArg, &helpArg, &cellArg, &resArg, &posArg,
&formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index child;
int valid = H3_EXPORT(isValidCell)(cell);
if (valid == 0) {
return E_CELL_INVALID;
}
H3Error err = H3_EXPORT(childPosToCell)(pos, cell, res, &child);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", child);
} else if (strcmp(format, "newline") == 0) {
h3Println(child);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
H3Index *readCellsFromFile(FILE *fp, char *buffer, size_t *totalCells) {
// It's assumed the buffer is a character array of size 1501 to support up
// to 100 cells at a time. If the file pointer is a null pointer, we assume
// the buffer has all of the possible cells already stored in it and go from
// there. Otherwise we continue reading from the file until it's fully
// consumed. On an error, we de-allocate the output buffer and then return
// 0. It's the responsibility of the caller to free the returned buffer and
// the file pointer otherwise. The output array's filled length is set on
// the cellsOffset pointer so it can be used by the caller.
H3Index *cells = calloc(128, sizeof(H3Index));
int cellsLen = 128;
int bufferOffset = 0;
int cellsOffset = 0;
do {
// Always start from the beginning of the buffer
bufferOffset = 0;
int lastGoodOffset = 0;
H3Index cell = 0;
while (bufferOffset <
BUFFER_SIZE_LESS_CELL) { // Keep consuming as much as possible
// A valid H3 cell is exactly 15 hexadecomical characters.
// Determine if we have a match, otherwise increment
int scanlen = 0;
sscanf(buffer + bufferOffset, "%" PRIx64 "%n", &cell, &scanlen);
if (scanlen != 15) {
cell = 0;
bufferOffset += 1;
} else {
bufferOffset += 16;
lastGoodOffset = bufferOffset;
}
// If we still don't have a cell and we've reached the end, we reset
// the offset and `continue` to trigger another read
if (cell == 0 && bufferOffset == BUFFER_SIZE) {
bufferOffset = 0;
continue;
} else if (cell != 0) {
// Otherwise, we have a cell to shove into the cells array.
cells[cellsOffset] = cell;
cellsOffset += 1;
// Potentially grow our array
if (cellsOffset == cellsLen) {
cellsLen *= 2;
H3Index *newCells = calloc(cellsLen, sizeof(H3Index));
for (int i = 0; i < cellsOffset; i++) {
newCells[i] = cells[i];
}
free(cells);
cells = newCells;
}
}
}
// In case there's a valid H3 index that was unfortunately split between
// buffer reads, we take from the lastGoodOffset and copy the rest to
// the beginning of the buffer so it can be re-assembled on the next
// file read. However, we also know that a valid H3 index is 15
// characters long, so if the lastGoodOffset is 15 or more characters
// away from 1500, we only need to copy those final 14 bytes from the
// end, so if lastGoodOffset is 1485 or less, we force it to 1485 and
// then move the chunk as specified to the beginning and adjust the
// cellStrsOffset.
if (lastGoodOffset < BUFFER_SIZE_LESS_CELL) {
lastGoodOffset = BUFFER_SIZE_LESS_CELL;
}
for (int i = 0; i < BUFFER_SIZE - lastGoodOffset; i++) {
buffer[i] = buffer[i + lastGoodOffset];
}
bufferOffset = BUFFER_SIZE - lastGoodOffset;
} while (fp != 0 && fread(buffer + bufferOffset, 1,
BUFFER_SIZE - bufferOffset, fp) != 0);
*totalCells = cellsOffset;
return cells;
}
SUBCOMMAND(compactCells,
"Compacts the provided set of cells as best as possible. The set of "
"input cells must all share the same resolution.") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
char filename[1024] = {0}; // More than Windows, lol
Arg filenameArg = {
.names = {"-i", "--file"},
.scanFormat = "%1023c",
.valueName = "FILENAME",
.value = &filename,
.helpText =
"The file to load the cells from. Use -- to read from stdin."};
char cellStrs[BUFFER_SIZE_WITH_NULL] = {
0}; // Up to 100 cells with zero padding
Arg cellStrsArg = {.names = {"-c", "--cells"},
// TODO: Can I use `BUFFER_SIZE` here somehow?
.scanFormat = "%1500c",
.valueName = "CELLS",
.value = &cellStrs,
.helpText =
"The cells to compact. Up to 100 cells if provided "
"as hexadecimals with zero padding."};
Arg *args[] = {&compactCellsArg, &helpArg, &filenameArg, &cellStrsArg,
&formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
if (!filenameArg.found && !cellStrsArg.found) {
fprintf(stderr,
"You must provide either a file to read from or a set of cells "
"to compact to use compactCells");
exit(1);
}
FILE *fp = 0;
bool isStdin = false;
if (filenameArg.found) {
if (strcmp(filename, "--") == 0) {
fp = stdin;
isStdin = true;
} else {
fp = fopen(filename, "r");
}
if (fp == 0) {
fprintf(stderr, "The specified file does not exist.");
exit(1);
}
// Do the initial population of data from the file
if (fread(cellStrs, 1, BUFFER_SIZE, fp) == 0) {
fprintf(stderr, "The specified file is empty.");
exit(1);
}
}
size_t cellsOffset = 0;
H3Index *cells = readCellsFromFile(fp, cellStrs, &cellsOffset);
if (fp != 0 && !isStdin) {
fclose(fp);
}
if (cells == NULL) {
return E_FAILED;
}
// Now that we have the cells in a buffer and the actual cell count in
// cellsOffset, we can feed this to the H3 C API
H3Index *compactedSet = calloc(cellsOffset, sizeof(H3Index));
H3Error err = H3_EXPORT(compactCells)(cells, compactedSet, cellsOffset);
if (err) {
free(cells);
free(compactedSet);
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// 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 < cellsOffset; i++) {
if (compactedSet[i] != 0) {
trueLen++;
}
}
printf("[ ");
for (int64_t i = 0, j = 0; i < cellsOffset; i++) {
if (compactedSet[i] != 0) {
j++;
printf("\"%" PRIx64 "\"%s", compactedSet[i],
j == trueLen ? "" : ", ");
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < cellsOffset; i++) {
if (compactedSet[i] != 0) {
h3Println(compactedSet[i]);
}
}
} else {
free(cells);
free(compactedSet);
return E_FAILED;
}
free(cells);
free(compactedSet);
return E_SUCCESS;
}
SUBCOMMAND(uncompactCells,
"Unompacts the provided set of compacted cells."
"The uncompacted "
"cells will be printed one per line to stdout.") {
// TODO: *Most* of this logic is shared with compactCells. See about DRYing
// it.
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
char filename[1024] = {0}; // More than Windows, lol
Arg filenameArg = {
.names = {"-i", "--file"},
.scanFormat = "%1023c",
.valueName = "FILENAME",
.value = &filename,
.helpText =
"The file to load the cells from. Use -- to read from stdin."};
char cellStrs[BUFFER_SIZE_WITH_NULL] = {
0}; // Supports up to 100 cells at a time with zero padding
Arg cellStrsArg = {
.names = {"-c", "--cells"},
.scanFormat = "%1500c",
.valueName = "CELLS",
.value = &cellStrs,
.helpText =
"The cells to uncompact. Up to 100 cells if provided "
"as hexadecimals with zero padding."};
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 0-15 inclusive, that the compacted set "
"should be uncompacted to. Must be greater than or equal "
"to the highest resolution within the compacted set."};
Arg *args[] = {&uncompactCellsArg, &helpArg, &filenameArg,
&cellStrsArg, &resArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
if (!filenameArg.found && !cellStrsArg.found) {
fprintf(stderr,
"You must provide either a file to read from or a set of cells "
"to compact to use uncompactCells");
exit(1);
}
// We use the same consumption logic for both kinds of input, and in fact
// use the cellStrs char array as out input buffer for the file path. The
// only difference between the two is when we reach the end of the buffer.
// If there's a non-null file pointer, we start from the last successful
// consumption from the buffer and move the data following it to the
// beginning of the buffer, then we read some of the file after that and
// slap it on after that and continue the consumption loops, while the other
// path just ends at that point. As we have no idea how many cells we're
// going to load, we allocate enough for 128 cells, but if that's not
// enough, we also have to re-allocate double the number of cells, copy them
// over, and free the old buffer of cells. Doing this manually since we want
// to keep the build process for H3 simple and C's stdlib is pretty bare.
FILE *fp = 0;
bool isStdin = false;
if (filenameArg.found) {
if (strcmp(filename, "--") == 0) {
fp = stdin;
isStdin = true;
} else {
fp = fopen(filename, "r");
}
if (fp == 0) {
fprintf(stderr, "The specified file does not exist.");
exit(1);
}
// Do the initial population of data from the file
if (fread(cellStrs, 1, BUFFER_SIZE, fp) == 0) {
fprintf(stderr, "The specified file is empty.");
exit(1);
}
}
size_t cellsOffset = 0;
H3Index *cells = readCellsFromFile(fp, cellStrs, &cellsOffset);
if (fp != 0 && !isStdin) {
fclose(fp);
}
if (cells == NULL) {
return E_FAILED;
}
// Now that we have the cells in a buffer and the actual cell count in
// cellsOffset, we can feed this to the H3 C API
int64_t uncompactedSize = 0;
H3Error err = H3_EXPORT(uncompactCellsSize)(cells, cellsOffset, res,
&uncompactedSize);
if (err) {
free(cells);
return err;
}
H3Index *uncompactedSet = calloc(uncompactedSize, sizeof(H3Index));
err = H3_EXPORT(uncompactCells)(cells, cellsOffset, uncompactedSet,
uncompactedSize, res);
if (err) {
free(cells);
free(uncompactedSet);
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// 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 < uncompactedSize; i++) {
if (uncompactedSet[i] != 0) {
trueLen++;
}
}
printf("[ ");
for (int64_t i = 0, j = 0; i < uncompactedSize; i++) {
if (uncompactedSet[i] != 0) {
j++;
printf("\"%" PRIx64 "\"%s", uncompactedSet[i],
j == trueLen ? "" : ", ");
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < uncompactedSize; i++) {
if (uncompactedSet[i] != 0) {
h3Println(uncompactedSet[i]);
}
}
} else {
free(cells);
free(uncompactedSet);
return E_FAILED;
}
free(cells);
free(uncompactedSet);
return E_SUCCESS;
}
/// Region subcommands
H3Error polygonStringToGeoPolygon(FILE *fp, char *polygonString,
GeoPolygon *out) {
// There are two kinds of valid input, an array of arrays of lat, lng values
// defining a singular polygon loop, or an array of array of arrays of lat,
// lng values defining a polygon and zero or more holes. Which kind of input
// this is can be determined by the `[` depth reached before the first
// floating point number is found. This means either 2 or 3 `[`s at the
// beginning are valid, and nothing more than that.
int8_t maxDepth = 0;
int8_t curDepth = 0;
int64_t numVerts = 6;
int64_t curVert = 0;
int64_t curLoop = 0;
LatLng *verts = calloc(numVerts, sizeof(LatLng));
int strPos = 0;
while (polygonString[strPos] != 0) {
// Load more of the file if we've hit our buffer limit
if (strPos >= BUFFER_SIZE && fp != 0) {
int result = fread(polygonString, 1, BUFFER_SIZE, fp);
strPos = 0;
// If we didn't get any data from the file, we're done.
if (result == 0) {
break;
}
}
// Try to match `[` first
if (polygonString[strPos] == '[') {
curDepth++;
if (curDepth > maxDepth) {
maxDepth = curDepth;
}
if (curDepth > 4) {
// This is beyond the depth for a valid input, so we abort early
free(verts);
return E_FAILED;
}
strPos++;
continue;
}
// Similar matching for `]`
if (polygonString[strPos] == ']') {
curDepth--;
if (curDepth < 0) {
break;
}
strPos++;
// We may need to set up a new geoloop at this point. If the
// curDepth <= maxDepth - 2 then we increment the curLoop and get a
// new one set up.
if (curDepth <= maxDepth - 2) {
if (curLoop == 0) {
out->geoloop.verts = verts;
out->geoloop.numVerts = curVert;
} else {
GeoLoop *holes = calloc(out->numHoles + 1, sizeof(GeoLoop));
if (holes == 0) {
return E_MEMORY_ALLOC;
}
for (int i = 0; i < out->numHoles; i++) {
holes[i].numVerts = out->holes[i].numVerts;
holes[i].verts = out->holes[i].verts;
}
free(out->holes);
holes[out->numHoles].verts = verts;
holes[out->numHoles].numVerts = curVert;
out->holes = holes;
out->numHoles++;
}
curLoop++;
curVert = 0;
numVerts = 6;
verts = calloc(numVerts, sizeof(LatLng));
}
continue;
}
// Try to grab a floating point value followed by optional whitespace, a
// comma, and more optional whitespace, and a second floating point
// value, then store the lat, lng pair
double lat, lng;
int count = 0;
// Must grab the closing ] or we might accidentally parse a partial
// float across buffer boundaries
char closeBracket[2] = "]";
int result = sscanf(polygonString + strPos, "%lf%*[, \n]%lf%1[]]%n",
&lat, &lng, &closeBracket[0], &count);
if (count > 0 && result == 3) {
verts[curVert].lat = H3_EXPORT(degsToRads)(lat);
verts[curVert].lng = H3_EXPORT(degsToRads)(lng);
curVert++;
// Create a new vert buffer, copy the old buffer, and free it, if
// necessary
if (curVert == numVerts) {
LatLng *newVerts = calloc(numVerts * 2, sizeof(LatLng));
for (int i = 0; i < numVerts; i++) {
newVerts[i].lat = verts[i].lat;
newVerts[i].lng = verts[i].lng;
}
free(verts);
verts = newVerts;
numVerts *= 2;
}
strPos += count;
curDepth--;
continue;
}
// Check for whitespace and skip it if we reach this point.
if (polygonString[strPos] == ',' || polygonString[strPos] == ' ' ||
polygonString[strPos] == '\n' || polygonString[strPos] == '\t' ||
polygonString[strPos] == '\r') {
strPos++;
continue;
}
if (strPos != 0 && fp != 0) {
// Scan the remaining of the current buffer for `0`. If not
// found, then we grab a new chunk and append to the remainder
// of the existing buffer. Otherwise, just skip unknown
// characters.
bool endOfFile = false;
for (int i = strPos; i <= BUFFER_SIZE; i++) {
if (polygonString[strPos] == 0) {
endOfFile = true;
break;
}
}
if (endOfFile) {
strPos++;
} else {
// Move the end of this buffer to the beginning
for (int i = strPos; i <= BUFFER_SIZE; i++) {
polygonString[i - strPos] = polygonString[i];
}
// Set the string position to the end of the buffer
strPos = BUFFER_SIZE - strPos;
// Grab up to the remaining size of the buffer and fill it
// into the file
// Did you know that if the amount you want to read from
// the file buffer is larger than the buffer itself,
// fread can choose to give you squat and not the
// remainder of the file as you'd expect from the
// documentation? This was a surprise to me and a
// significant amount of wasted time to figure out how
// to tackle. C leaves *way* too much as undefined
// behavior to be nice to work with...
int64_t i = 0;
int result;
do {
result = fread(polygonString + strPos + i, 1, 1, fp);
i++;
} while (i < BUFFER_SIZE - strPos && result != 0);
if (result == 0) {
polygonString[strPos + i - 1] = 0;
}
strPos = 0;
}
} else {
strPos++;
}
}
free(verts);
return E_SUCCESS;
}
SUBCOMMAND(
polygonToCells,
"Converts a polygon (array of lat, lng points, or array of arrays of lat, "
"lng points) into a set of covering cells at the specified resolution") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
char filename[1024] = {0}; // More than Windows, lol
Arg filenameArg = {
.names = {"-i", "--file"},
.scanFormat = "%1023c",
.valueName = "FILENAME",
.value = &filename,
.helpText =
"The file to load the polygon from. Use -- to read from stdin."};
char polygonStr[BUFFER_SIZE_WITH_NULL] = {
0}; // Up to 100 cells with zero padding
Arg polygonStrArg = {
.names = {"-p", "--polygon"},
.scanFormat = "%1500c",
.valueName = "POLYGON",
.value = &polygonStr,
.helpText = "The polygon to convert. Up to 1500 characters."};
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 0-15 inclusive, that the polygon "
"should be converted to."};
Arg *args[] = {&polygonToCellsArg, &helpArg, &filenameArg,
&polygonStrArg, &resArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
if (filenameArg.found == polygonStrArg.found) {
fprintf(stderr,
"You must provide either a file to read from or a polygon "
"to cover to use polygonToCells");
exit(1);
}
FILE *fp = 0;
bool isStdin = false;
if (filenameArg.found) {
if (strcmp(filename, "--") == 0) {
fp = stdin;
isStdin = true;
} else {
fp = fopen(filename, "r");
}
if (fp == 0) {
fprintf(stderr, "The specified file does not exist.");
exit(1);
}
// Do the initial population of data from the file
if (fread(polygonStr, 1, BUFFER_SIZE, fp) == 0) {
fprintf(stderr, "The specified file is empty.");
exit(1);
}
}
GeoPolygon polygon = {0};
H3Error err = polygonStringToGeoPolygon(fp, polygonStr, &polygon);
if (fp != 0 && !isStdin) {
fclose(fp);
}
if (err != E_SUCCESS) {
return err;
}
int64_t cellsSize = 0;
err = H3_EXPORT(maxPolygonToCellsSize)(&polygon, res, 0, &cellsSize);
if (err != E_SUCCESS) {
return err;
}
H3Index *cells = calloc(cellsSize, sizeof(H3Index));
err = H3_EXPORT(polygonToCells)(&polygon, res, 0, cells);
for (int i = 0; i < polygon.numHoles; i++) {
free(polygon.holes[i].verts);
}
free(polygon.holes);
free(polygon.geoloop.verts);
if (err != E_SUCCESS) {
free(cells);
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// 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 < cellsSize; i++) {
if (cells[i] != 0) {
trueLen++;
}
}
printf("[ ");
for (int64_t i = 0, j = 0; i < cellsSize; i++) {
if (cells[i] != 0) {
j++;
printf("\"%" PRIx64 "\"%s", cells[i], j == trueLen ? "" : ", ");
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < cellsSize; i++) {
if (cells[i] != 0) {
h3Println(cells[i]);
}
}
} else {
free(cells);
return E_FAILED;
}
free(cells);
return E_SUCCESS;
}
SUBCOMMAND(
maxPolygonToCellsSize,
"Returns the maximum number of cells that could be needed to cover "
"the polygon. Will always be equal or more than actually necessary") {
char filename[1024] = {0}; // More than Windows, lol
Arg filenameArg = {
.names = {"-i", "--file"},
.scanFormat = "%1023c",
.valueName = "FILENAME",
.value = &filename,
.helpText =
"The file to load the polygon from. Use -- to read from stdin."};
char polygonStr[BUFFER_SIZE_WITH_NULL] = {
0}; // Up to 100 cells with zero padding
Arg polygonStrArg = {
.names = {"-p", "--polygon"},
.scanFormat = "%1500c",
.valueName = "POLYGON",
.value = &polygonStr,
.helpText = "The polygon to convert. Up to 1500 characters."};
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText =
"Resolution, 0-15 inclusive, that the polygon "
"should be converted to."};
Arg *args[] = {&maxPolygonToCellsSizeArg, &helpArg, &filenameArg,
&polygonStrArg, &resArg};
PARSE_SUBCOMMAND(argc, argv, args);
if (filenameArg.found == polygonStrArg.found) {
fprintf(stderr,
"You must provide either a file to read from or a polygon "
"to cover to use maxPolygonToCellsSize");
exit(1);
}
FILE *fp = 0;
bool isStdin = false;
if (filenameArg.found) {
if (strcmp(filename, "--") == 0) {
fp = stdin;
isStdin = true;
} else {
fp = fopen(filename, "r");
}
if (fp == 0) {
fprintf(stderr, "The specified file does not exist.");
exit(1);
}
// Do the initial population of data from the file
if (fread(polygonStr, 1, BUFFER_SIZE, fp) == 0) {
fprintf(stderr, "The specified file is empty.");
exit(1);
}
}
GeoPolygon polygon = {0};
H3Error err = polygonStringToGeoPolygon(fp, polygonStr, &polygon);
if (fp != 0 && !isStdin) {
fclose(fp);
}
if (err != E_SUCCESS) {
return err;
}
int64_t cellsSize = 0;
err = H3_EXPORT(maxPolygonToCellsSize)(&polygon, res, 0, &cellsSize);
for (int i = 0; i < polygon.numHoles; i++) {
free(polygon.holes[i].verts);
}
free(polygon.holes);
free(polygon.geoloop.verts);
if (err != E_SUCCESS) {
return err;
}
printf("%" PRId64 "\n", cellsSize);
return E_SUCCESS;
}
SUBCOMMAND(cellsToMultiPolygon,
"Returns a polygon (array of arrays of "
"lat, lng points) for a set of cells") {
DEFINE_FORMAT_ARG(
"'json' for [[[[lat, lng],...],...],...] 'wkt' for a WKT MULTIPOLYGON");
char filename[1024] = {0}; // More than Windows, lol
Arg filenameArg = {
.names = {"-i", "--file"},
.scanFormat = "%1023c",
.valueName = "FILENAME",
.value = &filename,
.helpText =
"The file to load the cells from. Use -- to read from stdin."};
char cellStrs[BUFFER_SIZE_WITH_NULL] = {
0}; // Up to 100 cells with zero padding
Arg cellStrsArg = {.names = {"-c", "--cells"},
.scanFormat = "%1500c",
.valueName = "CELLS",
.value = &cellStrs,
.helpText =
"The cells to convert. Up to 100 cells if provided "
"as hexadecimals with zero padding."};
Arg *args[] = {&cellsToMultiPolygonArg, &helpArg, &filenameArg,
&cellStrsArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
if (filenameArg.found == cellStrsArg.found) {
fprintf(stderr,
"You must provide either a file to read from or a set of cells "
"to convert to use cellsToMultiPolygon");
exit(1);
}
FILE *fp = 0;
bool isStdin = false;
if (filenameArg.found) {
if (strcmp(filename, "--") == 0) {
fp = stdin;
isStdin = true;
} else {
fp = fopen(filename, "r");
}
if (fp == 0) {
fprintf(stderr, "The specified file does not exist.");
exit(1);
}
// Do the initial population of data from the file
if (fread(cellStrs, 1, BUFFER_SIZE, fp) == 0) {
fprintf(stderr, "The specified file is empty.");
exit(1);
}
}
size_t cellsOffset = 0;
H3Index *cells = readCellsFromFile(fp, cellStrs, &cellsOffset);
if (fp != 0 && !isStdin) {
fclose(fp);
}
if (cells == NULL) {
return E_FAILED;
}
LinkedGeoPolygon out = {0};
H3Error err =
H3_EXPORT(cellsToLinkedMultiPolygon)(cells, cellsOffset, &out);
if (err) {
free(cells);
H3_EXPORT(destroyLinkedMultiPolygon)(&out);
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[");
LinkedGeoPolygon *currPoly = &out;
while (currPoly) {
printf("[");
LinkedGeoLoop *currLoop = currPoly->first;
while (currLoop) {
printf("[");
LinkedLatLng *currLatLng = currLoop->first;
while (currLatLng) {
printf("[%f, %f]",
H3_EXPORT(radsToDegs)(currLatLng->vertex.lat),
H3_EXPORT(radsToDegs)(currLatLng->vertex.lng));
currLatLng = currLatLng->next;
if (currLatLng) {
printf(", ");
}
}
currLoop = currLoop->next;
if (currLoop) {
printf("], ");
} else {
printf("]");
}
}
currPoly = currPoly->next;
if (currPoly) {
printf("], ");
} else {
printf("]");
}
}
printf("]\n");
} else if (strcmp(format, "wkt") == 0) {
printf("MULTIPOLYGON (");
LinkedGeoPolygon *currPoly = &out;
while (currPoly) {
printf("(");
LinkedGeoLoop *currLoop = currPoly->first;
while (currLoop) {
printf("(");
LinkedLatLng *currLatLng = currLoop->first;
while (currLatLng) {
printf("%f %f",
H3_EXPORT(radsToDegs)(currLatLng->vertex.lng),
H3_EXPORT(radsToDegs)(currLatLng->vertex.lat));
currLatLng = currLatLng->next;
if (currLatLng) {
printf(", ");
}
}
currLoop = currLoop->next;
if (currLoop) {
printf("), ");
} else {
printf(")");
}
}
currPoly = currPoly->next;
if (currPoly) {
printf("), ");
} else {
printf(")");
}
}
printf(")\n");
} else {
free(cells);
H3_EXPORT(destroyLinkedMultiPolygon)(&out);
return E_FAILED;
}
free(cells);
H3_EXPORT(destroyLinkedMultiPolygon)(&out);
return E_SUCCESS;
}
/// Directed edge subcommands
SUBCOMMAND(areNeighborCells,
"Determines if the provided H3 cells are neighbors (have a shared "
"border)") {
DEFINE_FORMAT_ARG(
"'json' for true or false, 'numeric' for 1 or 0 (Default: json)");
H3Index origin, destination;
Arg originCellArg = {.names = {"-o", "--origin"},
.required = true,
.scanFormat = "%" PRIx64,
.valueName = "CELL",
.value = &origin,
.helpText = "Origin H3 Cell"};
Arg destinationCellArg = {.names = {"-d", "--destination"},
.required = true,
.scanFormat = "%" PRIx64,
.valueName = "CELL",
.value = &destination,
.helpText = "Destination H3 Cell"};
Arg *args[] = {&areNeighborCellsArg, &originCellArg, &destinationCellArg,
&helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
int areNeighbors = 0;
H3Error err =
H3_EXPORT(areNeighborCells)(origin, destination, &areNeighbors);
if (err != E_SUCCESS) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("%s\n", areNeighbors ? "true" : "false");
} else if (strcmp(format, "numeric") == 0) {
printf("%d\n", areNeighbors);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(cellsToDirectedEdge,
"Converts neighboring cells into a directed edge index (or errors "
"if they are not neighbors)") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n "
"(Default: json)");
H3Index origin, destination;
Arg originCellArg = {.names = {"-o", "--origin"},
.required = true,
.scanFormat = "%" PRIx64,
.valueName = "CELL",
.value = &origin,
.helpText = "Origin H3 Cell"};
Arg destinationCellArg = {.names = {"-d", "--destination"},
.required = true,
.scanFormat = "%" PRIx64,
.valueName = "CELL",
.value = &destination,
.helpText = "Destination H3 Cell"};
Arg *args[] = {&cellsToDirectedEdgeArg, &originCellArg, &destinationCellArg,
&helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index out = 0;
H3Error err = H3_EXPORT(cellsToDirectedEdge)(origin, destination, &out);
if (err != E_SUCCESS) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", out);
} else if (strcmp(format, "newline") == 0) {
h3Println(out);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(isValidDirectedEdge,
"Checks if the provided H3 directed edge is actually valid") {
DEFINE_FORMAT_ARG(
"'json' for true or false, 'numeric' for 1 or 0 (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&isValidDirectedEdgeArg, &helpArg, &cellArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
bool isValid = H3_EXPORT(isValidDirectedEdge)(cell);
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("%s\n", isValid ? "true" : "false");
} else if (strcmp(format, "numeric") == 0) {
printf("%d\n", isValid);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(getDirectedEdgeOrigin,
"Returns the origin cell from the directed edge") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n "
"(Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&getDirectedEdgeOriginArg, &cellArg, &helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index out = 0;
H3Error err = H3_EXPORT(getDirectedEdgeOrigin)(cell, &out);
if (err != E_SUCCESS) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", out);
} else if (strcmp(format, "newline") == 0) {
h3Println(out);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(getDirectedEdgeDestination,
"Returns the destination cell from the directed edge") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n "
"(Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&getDirectedEdgeDestinationArg, &cellArg, &helpArg,
&formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index out = 0;
H3Error err = H3_EXPORT(getDirectedEdgeDestination)(cell, &out);
if (err != E_SUCCESS) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", out);
} else if (strcmp(format, "newline") == 0) {
h3Println(out);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(
directedEdgeToCells,
"Returns the origin, destination pair of cells from the directed edge") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&directedEdgeToCellsArg, &cellArg, &helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index out[2] = {0};
H3Error err = H3_EXPORT(directedEdgeToCells)(cell, &out[0]);
if (err != E_SUCCESS) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[\"%" PRIx64 "\", \"%" PRIx64 "\"]\n", out[0], out[1]);
} else if (strcmp(format, "newline") == 0) {
printf("%" PRIx64 "\n%" PRIx64 "\n", out[0], out[1]);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(originToDirectedEdges,
"Returns all of the directed edges from the specified origin cell") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&originToDirectedEdgesArg, &cellArg, &helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index out[6] = {0};
// This one is pretty loose about the inputs it accepts, so let's validate
// for it
bool isValid = H3_EXPORT(isValidCell)(cell);
if (!isValid) {
return E_CELL_INVALID;
}
H3Error err = H3_EXPORT(originToDirectedEdges)(cell, &out[0]);
if (err != E_SUCCESS) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[");
bool hasPrinted = false;
for (int i = 0; i < 6; i++) {
if (out[i] > 0) {
if (hasPrinted) {
printf(", ");
}
printf("\"%" PRIx64 "\"", out[i]);
hasPrinted = true;
}
}
printf("]\n");
} else if (strcmp(format, "newline") == 0) {
for (int i = 0; i < 6; i++) {
if (out[i] != 0) {
h3Println(out[i]);
}
}
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(directedEdgeToBoundary,
"Provides the coordinates defining the directed edge") {
DEFINE_FORMAT_ARG(
"'json' for [[lat, lng], ...], 'wkt' for a WKT POLYGON, 'newline' for "
"lat\\nlng\\n... (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&directedEdgeToBoundaryArg, &cellArg, &helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
CellBoundary cb = {0};
H3Error err = H3_EXPORT(directedEdgeToBoundary)(cell, &cb);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[");
for (int i = 0; i < cb.numVerts - 1; i++) {
LatLng *ll = &cb.verts[i];
printf("[%.10lf, %.10lf], ", H3_EXPORT(radsToDegs)(ll->lat),
H3_EXPORT(radsToDegs)(ll->lng));
}
printf("[%.10lf, %.10lf]]\n",
H3_EXPORT(radsToDegs)(cb.verts[cb.numVerts - 1].lat),
H3_EXPORT(radsToDegs)(cb.verts[cb.numVerts - 1].lng));
} else if (strcmp(format, "wkt") == 0) {
printf("LINESTRING (");
for (int i = 0; i < cb.numVerts - 1; i++) {
LatLng *ll = &cb.verts[i];
printf("%.10lf %.10lf, ", H3_EXPORT(radsToDegs)(ll->lng),
H3_EXPORT(radsToDegs)(ll->lat));
}
printf("%.10lf %.10lf)\n",
H3_EXPORT(radsToDegs)(cb.verts[cb.numVerts - 1].lng),
H3_EXPORT(radsToDegs)(cb.verts[cb.numVerts - 1].lat));
} else if (strcmp(format, "newline") == 0) {
for (int i = 0; i < cb.numVerts; i++) {
LatLng *ll = &cb.verts[i];
printf("%.10lf\n%.10lf\n", H3_EXPORT(radsToDegs)(ll->lat),
H3_EXPORT(radsToDegs)(ll->lng));
}
} else {
return E_FAILED;
}
return E_SUCCESS;
}
/// Vertex subcommands
SUBCOMMAND(cellToVertex,
"Returns the vertex for the specified cell and vertex index. Must "
"be 0-5 for hexagons, 0-4 for pentagons") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\"\\n, 'newline' for CELL\\n "
"(Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
int vertIndex = 0;
Arg vertIndexArg = {
.names = {"-v", "--vertex"},
.required = true,
.scanFormat = "%d",
.valueName = "INDEX",
.value = &vertIndex,
.helpText = "Vertex index number. 0-5 for hexagons, 0-4 for pentagons"};
Arg *args[] = {&cellToVertexArg, &cellArg, &vertIndexArg, &helpArg,
&formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
// This function also doesn't sanitize its inputs correctly
bool isValid = H3_EXPORT(isValidCell)(cell);
if (!isValid) {
return E_CELL_INVALID;
}
H3Index out = 0;
H3Error err = H3_EXPORT(cellToVertex)(cell, vertIndex, &out);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%" PRIx64 "\"\n", out);
} else if (strcmp(format, "newline") == 0) {
h3Println(out);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(cellToVertexes,
"Returns all of the vertexes from the specified cell") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&cellToVertexesArg, &cellArg, &helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index out[6] = {0};
// This one is pretty loose about the inputs it accepts, so let's validate
// for it
bool isValid = H3_EXPORT(isValidCell)(cell);
if (!isValid) {
return E_CELL_INVALID;
}
H3Error err = H3_EXPORT(cellToVertexes)(cell, &out[0]);
if (err != E_SUCCESS) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// 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 < 6; i++) {
if (out[i] != 0) {
trueLen++;
}
}
printf("[ ");
for (int64_t i = 0, j = 0; i < 6; i++) {
if (out[i] != 0) {
j++;
printf("\"%" PRIx64 "\"%s", out[i], j == trueLen ? "" : ", ");
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < 6; i++) {
if (out[i] != 0) {
h3Println(out[i]);
}
}
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(vertexToLatLng, "Returns the lat, lng pair for the given vertex") {
DEFINE_FORMAT_ARG(
"'json' for [lat, lng], 'wkt' for a WKT POINT, 'newline' for "
"lat\\nlng\\n (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&vertexToLatLngArg, &cellArg, &helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
bool isValid = H3_EXPORT(isValidVertex)(cell);
if (!isValid) {
return E_VERTEX_INVALID;
}
LatLng ll;
H3Error err = H3_EXPORT(vertexToLatLng)(cell, &ll);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("[%.10lf, %.10lf]\n", H3_EXPORT(radsToDegs)(ll.lat),
H3_EXPORT(radsToDegs)(ll.lng));
} else if (strcmp(format, "wkt") == 0) {
// Using WKT formatting for the output. TODO: Add support for JSON
// formatting
printf("POINT(%.10lf %.10lf)\n", H3_EXPORT(radsToDegs)(ll.lng),
H3_EXPORT(radsToDegs)(ll.lat));
} else if (strcmp(format, "newline") == 0) {
printf("%.10lf\n%.10lf\n", H3_EXPORT(radsToDegs)(ll.lat),
H3_EXPORT(radsToDegs)(ll.lng));
} else {
return E_FAILED;
}
return E_SUCCESS;
}
SUBCOMMAND(isValidVertex,
"Checks if the provided H3 vertex is actually valid") {
DEFINE_FORMAT_ARG(
"'json' for true or false, 'numeric' for 1 or 0 (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&isValidVertexArg, &helpArg, &cellArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
bool isValid = H3_EXPORT(isValidVertex)(cell);
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("%s\n", isValid ? "true" : "false");
} else if (strcmp(format, "numeric") == 0) {
printf("%d\n", isValid);
} else {
return E_FAILED;
}
return E_SUCCESS;
}
/// Miscellaneous subcommands
SUBCOMMAND(degsToRads, "Converts degrees to radians") {
double deg = 0;
Arg degArg = {.names = {"-d", "--degree"},
.required = true,
.scanFormat = "%lf",
.valueName = "DEG",
.value = °,
.helpText = "Angle in degrees"};
Arg *args[] = {°sToRadsArg, °Arg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
printf("%.10lf\n", H3_EXPORT(degsToRads)(deg));
return E_SUCCESS;
}
SUBCOMMAND(radsToDegs, "Converts radians to degrees") {
double rad = 0;
Arg radArg = {.names = {"-r", "--radian"},
.required = true,
.scanFormat = "%lf",
.valueName = "RAD",
.value = &rad,
.helpText = "Angle in radians"};
Arg *args[] = {&radsToDegsArg, &radArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
printf("%.10lf\n", H3_EXPORT(radsToDegs)(rad));
return E_SUCCESS;
}
SUBCOMMAND(getHexagonAreaAvgKm2,
"The average area in square kilometers for a hexagon of a given "
"resolution (excludes pentagons)") {
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText = "Resolution, 0-15 inclusive."};
Arg *args[] = {&getHexagonAreaAvgKm2Arg, &resArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
double area = 0;
H3Error err = H3_EXPORT(getHexagonAreaAvgKm2)(res, &area);
if (err) {
return err;
}
printf("%.10lf\n", area);
return E_SUCCESS;
}
SUBCOMMAND(getHexagonAreaAvgM2,
"The average area in square meters for a hexagon of a given "
"resolution (excludes pentagons)") {
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText = "Resolution, 0-15 inclusive."};
Arg *args[] = {&getHexagonAreaAvgM2Arg, &resArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
double area = 0;
H3Error err = H3_EXPORT(getHexagonAreaAvgM2)(res, &area);
if (err) {
return err;
}
printf("%.10lf\n", area);
return E_SUCCESS;
}
SUBCOMMAND(cellAreaRads2,
"The exact area of a specific cell in square radians") {
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&cellAreaRads2Arg, &cellArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
// This one is pretty loose about the inputs it accepts, so let's validate
// for it
bool isValid = H3_EXPORT(isValidCell)(cell);
if (!isValid) {
return E_CELL_INVALID;
}
double area = 0;
H3Error err = H3_EXPORT(cellAreaRads2)(cell, &area);
if (err) {
return err;
}
printf("%.10lf\n", area);
return E_SUCCESS;
}
SUBCOMMAND(cellAreaKm2,
"The exact area of a specific cell in square kilometers") {
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&cellAreaKm2Arg, &cellArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
// This one is pretty loose about the inputs it accepts, so let's validate
// for it
bool isValid = H3_EXPORT(isValidCell)(cell);
if (!isValid) {
return E_CELL_INVALID;
}
double area = 0;
H3Error err = H3_EXPORT(cellAreaKm2)(cell, &area);
if (err) {
return err;
}
printf("%.10lf\n", area);
return E_SUCCESS;
}
SUBCOMMAND(cellAreaM2, "The exact area of a specific cell in square meters") {
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&cellAreaM2Arg, &cellArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
// This one is pretty loose about the inputs it accepts, so let's validate
// for it
bool isValid = H3_EXPORT(isValidCell)(cell);
if (!isValid) {
return E_CELL_INVALID;
}
double area = 0;
H3Error err = H3_EXPORT(cellAreaM2)(cell, &area);
if (err) {
return err;
}
printf("%.10lf\n", area);
return E_SUCCESS;
}
SUBCOMMAND(getHexagonEdgeLengthAvgKm,
"The average hexagon edge length in kilometers of a given "
"resolution (excludes pentagons)") {
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText = "Resolution, 0-15 inclusive."};
Arg *args[] = {&getHexagonEdgeLengthAvgKmArg, &resArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
double area = 0;
H3Error err = H3_EXPORT(getHexagonEdgeLengthAvgKm)(res, &area);
if (err) {
return err;
}
printf("%.10lf\n", area);
return E_SUCCESS;
}
SUBCOMMAND(getHexagonEdgeLengthAvgM,
"The average hexagon edge length in meters of a given "
"resolution (excludes pentagons)") {
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText = "Resolution, 0-15 inclusive."};
Arg *args[] = {&getHexagonEdgeLengthAvgMArg, &resArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
double area = 0;
H3Error err = H3_EXPORT(getHexagonEdgeLengthAvgM)(res, &area);
if (err) {
return err;
}
printf("%.10lf\n", area);
return E_SUCCESS;
}
SUBCOMMAND(edgeLengthRads,
"The exact edge length of a specific directed edge in radians") {
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&edgeLengthRadsArg, &cellArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
// This one is pretty loose about the inputs it accepts, so let's validate
// for it
bool isValid = H3_EXPORT(isValidDirectedEdge)(cell);
if (!isValid) {
return E_DIR_EDGE_INVALID;
}
double length = 0;
H3Error err = H3_EXPORT(edgeLengthRads)(cell, &length);
if (err) {
return err;
}
printf("%.10lf\n", length);
return E_SUCCESS;
}
SUBCOMMAND(edgeLengthKm,
"The exact edge length of a specific directed edge in kilometers") {
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&edgeLengthKmArg, &cellArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
// This one is pretty loose about the inputs it accepts, so let's validate
// for it
bool isValid = H3_EXPORT(isValidDirectedEdge)(cell);
if (!isValid) {
return E_DIR_EDGE_INVALID;
}
double length = 0;
H3Error err = H3_EXPORT(edgeLengthKm)(cell, &length);
if (err) {
return err;
}
printf("%.10lf\n", length);
return E_SUCCESS;
}
SUBCOMMAND(edgeLengthM,
"The exact edge length of a specific directed edge in meters") {
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&edgeLengthMArg, &cellArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
// This one is pretty loose about the inputs it accepts, so let's validate
// for it
bool isValid = H3_EXPORT(isValidDirectedEdge)(cell);
if (!isValid) {
return E_DIR_EDGE_INVALID;
}
double length = 0;
H3Error err = H3_EXPORT(edgeLengthM)(cell, &length);
if (err) {
return err;
}
printf("%.10lf\n", length);
return E_SUCCESS;
}
SUBCOMMAND(getNumCells,
"The number of unique H3 cells for a specified resolution") {
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText = "Resolution, 0-15 inclusive."};
Arg *args[] = {&getNumCellsArg, &resArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
int64_t numCells = 0;
H3Error err = H3_EXPORT(getNumCells)(res, &numCells);
if (err) {
return err;
}
printf("%" PRId64 "\n", numCells);
return E_SUCCESS;
}
SUBCOMMAND(getRes0Cells, "Returns all of the resolution 0 cells") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
Arg *args[] = {&getRes0CellsArg, &helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index *out = calloc(122, sizeof(H3Index));
H3Error err = H3_EXPORT(getRes0Cells)(out);
if (err != E_SUCCESS) {
free(out);
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// 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 < 122; i++) {
if (out[i] != 0) {
trueLen++;
}
}
printf("[ ");
for (int64_t i = 0, j = 0; i < 122; i++) {
if (out[i] != 0) {
j++;
printf("\"%" PRIx64 "\"%s", out[i], j == trueLen ? "" : ", ");
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < 122; i++) {
if (out[i] != 0) {
h3Println(out[i]);
}
}
} else {
free(out);
return E_FAILED;
}
free(out);
return E_SUCCESS;
}
SUBCOMMAND(getPentagons,
"Returns all of the pentagons at the specified resolution") {
DEFINE_FORMAT_ARG(
"'json' for [\"CELL\", ...], 'newline' for CELL\\n... (Default: json)");
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText = "Resolution, 0-15 inclusive."};
Arg *args[] = {&getPentagonsArg, &resArg, &helpArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);
H3Index *out = calloc(12, sizeof(H3Index));
H3Error err = H3_EXPORT(getPentagons)(res, out);
if (err != E_SUCCESS) {
free(out);
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
// 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 < 12; i++) {
if (out[i] != 0) {
trueLen++;
}
}
printf("[ ");
for (int64_t i = 0, j = 0; i < 12; i++) {
if (out[i] != 0) {
j++;
printf("\"%" PRIx64 "\"%s", out[i], j == trueLen ? "" : ", ");
}
}
printf(" ]\n");
} else if (strcmp(format, "newline") == 0) {
for (int64_t i = 0; i < 12; i++) {
if (out[i] != 0) {
h3Println(out[i]);
}
}
} else {
free(out);
return E_FAILED;
}
free(out);
return E_SUCCESS;
}
SUBCOMMAND(pentagonCount, "Returns 12") {
Arg *args[] = {&pentagonCountArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
printf("12\n");
return E_SUCCESS;
}
SUBCOMMAND(greatCircleDistanceRads,
"Calculates the 'great circle' or 'haversine' distance between two "
"lat, lng points, in radians") {
char filename[1024] = {0}; // More than Windows, lol
Arg filenameArg = {.names = {"-i", "--file"},
.scanFormat = "%1023c",
.valueName = "FILENAME",
.value = &filename,
.helpText =
"The file to load the coordinates from. Use -- to "
"read from stdin."};
char coordinateStr[1501] = {0};
Arg coordinateStrArg = {
.names = {"-c", "--coordinates"},
.scanFormat = "%1500c",
.valueName = "ARRAY",
.value = &coordinateStr,
.helpText =
"The array of coordinates to convert. Up to 1500 characters."};
Arg *args[] = {&greatCircleDistanceRadsArg, &filenameArg, &coordinateStrArg,
&helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
if (filenameArg.found == coordinateStrArg.found) {
fprintf(
stderr,
"You must provide either a file to read from or a coordinate array "
"to use greatCircleDistanceRads");
exit(1);
}
FILE *fp = 0;
bool isStdin = false;
if (filenameArg.found) {
if (strcmp(filename, "--") == 0) {
fp = stdin;
isStdin = true;
} else {
fp = fopen(filename, "r");
}
if (fp == 0) {
fprintf(stderr, "The specified file does not exist.");
exit(1);
}
// Do the initial population of data from the file
if (fread(coordinateStr, 1, BUFFER_SIZE, fp) == 0) {
fprintf(stderr, "The specified file is empty.");
exit(1);
}
}
GeoPolygon polygon = {0};
H3Error err = polygonStringToGeoPolygon(fp, coordinateStr, &polygon);
if (fp != 0 && !isStdin) {
fclose(fp);
}
if (err != E_SUCCESS) {
return err;
}
if (polygon.numHoles > 0 || polygon.geoloop.numVerts != 2) {
fprintf(stderr, "Only two pairs of coordinates should be provided.");
exit(1);
}
double distance = H3_EXPORT(greatCircleDistanceRads)(
&polygon.geoloop.verts[0], &polygon.geoloop.verts[1]);
printf("%.10lf\n", distance);
for (int i = 0; i < polygon.numHoles; i++) {
free(polygon.holes[i].verts);
}
free(polygon.holes);
free(polygon.geoloop.verts);
return E_SUCCESS;
}
SUBCOMMAND(greatCircleDistanceKm,
"Calculates the 'great circle' or 'haversine' distance between two "
"lat, lng points, in kilometers") {
char filename[1024] = {0}; // More than Windows, lol
Arg filenameArg = {.names = {"-i", "--file"},
.scanFormat = "%1023c",
.valueName = "FILENAME",
.value = &filename,
.helpText =
"The file to load the coordinates from. Use -- to "
"read from stdin."};
char coordinateStr[1501] = {0};
Arg coordinateStrArg = {
.names = {"-c", "--coordinates"},
.scanFormat = "%1500c",
.valueName = "ARRAY",
.value = &coordinateStr,
.helpText =
"The array of coordinates to convert. Up to 1500 characters."};
Arg *args[] = {&greatCircleDistanceKmArg, &filenameArg, &coordinateStrArg,
&helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
if (filenameArg.found == coordinateStrArg.found) {
fprintf(
stderr,
"You must provide either a file to read from or a coordinate array "
"to use greatCircleDistanceKm");
exit(1);
}
FILE *fp = 0;
bool isStdin = false;
if (filenameArg.found) {
if (strcmp(filename, "--") == 0) {
fp = stdin;
isStdin = true;
} else {
fp = fopen(filename, "r");
}
if (fp == 0) {
fprintf(stderr, "The specified file does not exist.");
exit(1);
}
// Do the initial population of data from the file
if (fread(coordinateStr, 1, BUFFER_SIZE, fp) == 0) {
fprintf(stderr, "The specified file is empty.");
exit(1);
}
}
GeoPolygon polygon = {0};
H3Error err = polygonStringToGeoPolygon(fp, coordinateStr, &polygon);
if (fp != 0 && !isStdin) {
fclose(fp);
}
if (err != E_SUCCESS) {
return err;
}
if (polygon.numHoles > 0 || polygon.geoloop.numVerts != 2) {
fprintf(stderr, "Only two pairs of coordinates should be provided.");
exit(1);
}
double distance = H3_EXPORT(greatCircleDistanceKm)(
&polygon.geoloop.verts[0], &polygon.geoloop.verts[1]);
printf("%.10lf\n", distance);
for (int i = 0; i < polygon.numHoles; i++) {
free(polygon.holes[i].verts);
}
free(polygon.holes);
free(polygon.geoloop.verts);
return E_SUCCESS;
}
SUBCOMMAND(greatCircleDistanceM,
"Calculates the 'great circle' or 'haversine' distance between two "
"lat, lng points, in meters") {
char filename[1024] = {0}; // More than Windows, lol
Arg filenameArg = {.names = {"-i", "--file"},
.scanFormat = "%1023c",
.valueName = "FILENAME",
.value = &filename,
.helpText =
"The file to load the coordinates from. Use -- to "
"read from stdin."};
char coordinateStr[1501] = {0};
Arg coordinateStrArg = {
.names = {"-c", "--coordinates"},
.scanFormat = "%1500c",
.valueName = "ARRAY",
.value = &coordinateStr,
.helpText =
"The array of coordinates to convert. Up to 1500 characters."};
Arg *args[] = {&greatCircleDistanceMArg, &filenameArg, &coordinateStrArg,
&helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
if (filenameArg.found == coordinateStrArg.found) {
fprintf(
stderr,
"You must provide either a file to read from or a coordinate array "
"to use greatCircleDistanceM");
exit(1);
}
FILE *fp = 0;
bool isStdin = false;
if (filenameArg.found) {
if (strcmp(filename, "--") == 0) {
fp = stdin;
isStdin = true;
} else {
fp = fopen(filename, "r");
}
if (fp == 0) {
fprintf(stderr, "The specified file does not exist.");
exit(1);
}
// Do the initial population of data from the file
if (fread(coordinateStr, 1, BUFFER_SIZE, fp) == 0) {
fprintf(stderr, "The specified file is empty.");
exit(1);
}
}
GeoPolygon polygon = {0};
H3Error err = polygonStringToGeoPolygon(fp, coordinateStr, &polygon);
if (fp != 0 && !isStdin) {
fclose(fp);
}
if (err != E_SUCCESS) {
return err;
}
if (polygon.numHoles > 0 || polygon.geoloop.numVerts != 2) {
fprintf(stderr, "Only two pairs of coordinates should be provided.");
exit(1);
}
double distance = H3_EXPORT(greatCircleDistanceM)(
&polygon.geoloop.verts[0], &polygon.geoloop.verts[1]);
printf("%.10lf\n", distance);
for (int i = 0; i < polygon.numHoles; i++) {
free(polygon.holes[i].verts);
}
free(polygon.holes);
free(polygon.geoloop.verts);
return E_SUCCESS;
}
SUBCOMMAND(describeH3Error,
"Returns a description of the provided H3 error code number, or "
"indicates the number is itself invalid.") {
H3Error err = E_SUCCESS;
Arg errArg = {.names = {"-e", "--error"},
.required = true,
.scanFormat = "%d",
.valueName = "CODE",
.value = &err,
.helpText = "H3 Error code to describe"};
Arg *args[] = {&describeH3ErrorArg, &errArg, &helpArg};
PARSE_SUBCOMMAND(argc, argv, args);
printf("%s\n", H3_EXPORT(describeH3Error)(err));
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)
/// Traversal subcommands
SUBCOMMAND_INDEX(gridDisk)
SUBCOMMAND_INDEX(gridDiskDistances)
SUBCOMMAND_INDEX(gridRing)
SUBCOMMAND_INDEX(gridPathCells)
SUBCOMMAND_INDEX(gridDistance)
SUBCOMMAND_INDEX(cellToLocalIj)
SUBCOMMAND_INDEX(localIjToCell)
/// Hierarchical subcommands
SUBCOMMAND_INDEX(cellToParent)
SUBCOMMAND_INDEX(cellToChildren)
SUBCOMMAND_INDEX(cellToChildrenSize)
SUBCOMMAND_INDEX(cellToCenterChild)
SUBCOMMAND_INDEX(cellToChildPos)
SUBCOMMAND_INDEX(childPosToCell)
SUBCOMMAND_INDEX(compactCells)
SUBCOMMAND_INDEX(uncompactCells)
/// Region subcommands
SUBCOMMAND_INDEX(polygonToCells)
SUBCOMMAND_INDEX(maxPolygonToCellsSize)
SUBCOMMAND_INDEX(cellsToMultiPolygon)
/// Directed Edge subcommands
SUBCOMMAND_INDEX(areNeighborCells)
SUBCOMMAND_INDEX(cellsToDirectedEdge)
SUBCOMMAND_INDEX(isValidDirectedEdge)
SUBCOMMAND_INDEX(getDirectedEdgeOrigin)
SUBCOMMAND_INDEX(getDirectedEdgeDestination)
SUBCOMMAND_INDEX(directedEdgeToCells)
SUBCOMMAND_INDEX(originToDirectedEdges)
SUBCOMMAND_INDEX(directedEdgeToBoundary)
/// Vertex subcommands
SUBCOMMAND_INDEX(cellToVertex)
SUBCOMMAND_INDEX(cellToVertexes)
SUBCOMMAND_INDEX(vertexToLatLng)
SUBCOMMAND_INDEX(isValidVertex)
/// Miscellaneous subcommands
SUBCOMMAND_INDEX(degsToRads)
SUBCOMMAND_INDEX(radsToDegs)
SUBCOMMAND_INDEX(getHexagonAreaAvgKm2)
SUBCOMMAND_INDEX(getHexagonAreaAvgM2)
SUBCOMMAND_INDEX(cellAreaRads2)
SUBCOMMAND_INDEX(cellAreaKm2)
SUBCOMMAND_INDEX(cellAreaM2)
SUBCOMMAND_INDEX(getHexagonEdgeLengthAvgKm)
SUBCOMMAND_INDEX(getHexagonEdgeLengthAvgM)
SUBCOMMAND_INDEX(edgeLengthRads)
SUBCOMMAND_INDEX(edgeLengthKm)
SUBCOMMAND_INDEX(edgeLengthM)
SUBCOMMAND_INDEX(getNumCells)
SUBCOMMAND_INDEX(getRes0Cells)
SUBCOMMAND_INDEX(getPentagons)
SUBCOMMAND_INDEX(pentagonCount)
SUBCOMMAND_INDEX(greatCircleDistanceRads)
SUBCOMMAND_INDEX(greatCircleDistanceKm)
SUBCOMMAND_INDEX(greatCircleDistanceM)
SUBCOMMAND_INDEX(describeH3Error)
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;
}