8000 Encode "extra" information in unused digits of H3 indexes by isaacbrodsky · Pull Request #1025 · uber/h3 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Encode "extra" information in unused digits of H3 indexes #1025

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ The public API of this library consists of the functions declared in file
[h3api.h.in](./src/h3lib/include/h3api.h.in).

## [Unreleased]
### Added
- Added `getUnusedDigits`, `setUnusedDigits`, and `getMaxUnusedDigits` functions for encoding
and decoding data from the unused indexing digits of an H3 cell or other index.

## [4.3.0] - 2025-06-17
### Added
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ set(OTHER_SOURCE_FILES
src/apps/testapps/testLinkedGeoInternal.c
src/apps/testapps/mkRandGeo.c
src/apps/testapps/testH3Api.c
src/apps/testapps/testIndexDigits.c
src/apps/testapps/testCellsToLinkedMultiPolygon.c
src/apps/testapps/testCellToLocalIj.c
src/apps/testapps/testCellToLocalIjInternal.c
Expand Down
1 change: 1 addition & 0 deletions CMakeTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ add_h3_test(testCellToChildrenSize src/apps/testapps/testCellToChildrenSize.c)
add_h3_test(testH3Index src/apps/testapps/testH3Index.c)
add_h3_test(testH3IndexInternal src/apps/testapps/testH3IndexInternal.c)
add_h3_test(testH3Api src/apps/testapps/testH3Api.c)
add_h3_test(testIndexDigits src/apps/testapps/testIndexDigits.c)
add_h3_test(testCellsToLinkedMultiPolygon
src/apps/testapps/testCellsToLinkedMultiPolygon.c)
add_h3_test(testH3SetToVertexGraphInternal
Expand Down
82 changes: 81 additions & 1 deletion src/apps/filters/h3.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,83 @@ SUBCOMMAND(getBaseCellNumber,
return E_SUCCESS;
}

SUBCOMMAND(getMaxUnusedDigits,
"Returns the maximum value that can be provided to setUnusedDigits "
"for a resolution") {
DEFINE_FORMAT_ARG("Output format: decimal (default), or hex.");
int res = 0;
Arg resArg = {.names = {"-r", "--resolution"},
.required = true,
.scanFormat = "%d",
.valueName = "res",
.value = &res,
.helpText = "Resolution, 0-15 inclusive."};
Arg *args[] = {&getMaxUnusedDigitsArg, &helpArg, &resArg, &formatArg};
PARSE_SUBCOMMAND(argc, argv, args);

H3Index value;
H3Error err = H3_EXPORT(getMaxUnusedDigits)(res, &value);
if (err) {
return err;
}
if (strcmp(format, "decimal") == 0 || strcmp(format, "") == 0) {
printf("%lld\n", value);
} else if (strcmp(format, "hex") == 0) {
printf("%llx\n", value);
} else {
return E_FAILED;
}
return E_SUCCESS;
}

SUBCOMMAND(getUnusedDigits, "Returns value of the unused digits for an index") {
DEFINE_FORMAT_ARG("Output format: decimal (default), or hex.");
DEFINE_CELL_ARG(cell, cellArg);
Arg *args[] = {&getUnusedDigitsArg, &helpArg, &formatArg, &cellArg};
PARSE_SUBCOMMAND(argc, argv, args);

H3Index value = H3_EXPORT(getUnusedDigits)(cell);
if (strcmp(format, "decimal") == 0 || strcmp(format, "") == 0) {
printf("%lld\n", value);
} else if (strcmp(format, "hex") == 0) {
printf("%llx\n", value);
} else {
return E_FAILED;
}
return E_SUCCESS;
}

SUBCOMMAND(setUnusedDigits,
"Creates an index with modified unused digits for an index") {
DEFINE_FORMAT_ARG(
"'json' for \"CELL\", 'newline' for CELL (Default: json)");
DEFINE_CELL_ARG(cell, cellArg);
H3Index data = 0;
Arg dataArg = {.names = {"-d", "--data"},
.required = true,
.scanFormat = "%" PRId64,
.valueName = "data",
.value = &data,
.helpText = "Value to encode"};
Arg *args[] = {&setUnusedDigitsArg, &helpArg, &formatArg, &cellArg,
&dataArg};
PARSE_SUBCOMMAND(argc, argv, args);

H3Index value;
H3Error err = H3_EXPORT(setUnusedDigits)(cell, data, &value);
if (err) {
return err;
}
if (strcmp(format, "json") == 0 || strcmp(format, "") == 0) {
printf("\"%llx\"\n", value);
} else if (strcmp(format, "newline") == 0) {
h3Println(value);
} else {
return E_FAILED;
}
return E_SUCCESS;
}

SUBCOMMAND(stringToInt, "Converts an H3 index in string form to integer form") {
char *rawCell = calloc(16, sizeof(char));
if (rawCell == NULL) {
Expand All @@ -304,7 +381,7 @@ SUBCOMMAND(stringToInt, "Converts an H3 index in string form to integer form") {
}
Arg rawCellArg = {.names = {"-c", "--cell"},
.required = true,
.scanFormat = "%s",
.scanFormat = "%15s",
.valueName = "cell",
.value = rawCell,
.helpText = "H3 Cell Index"};
6D47 Expand Down Expand Up @@ -2888,6 +2965,9 @@ SUBCOMMAND_INDEX(cellToBoundary)
/// Inspection subcommands
SUBCOMMAND_INDEX(getResolution)
SUBCOMMAND_INDEX(getBaseCellNumber)
SUBCOMMAND_INDEX(getMaxUnusedDigits)
SUBCOMMAND_INDEX(getUnusedDigits)
SUBCOMMAND_INDEX(setUnusedDigits)
SUBCOMMAND_INDEX(stringToInt)
SUBCOMMAND_INDEX(intToString)
SUBCOMMAND_INDEX(isValidCell)
Expand Down
3 changes: 3 additions & 0 deletions src/apps/fuzzers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ The public API of H3 is covered in the following fuzzers:
| distance | [fuzzerDistances](./fuzzerDistances.c)
| edgeLength | [fuzzerEdgeLength](./fuzzerEdgeLength.c)
| getBaseCellNumber | [fuzzerCellProperties](./fuzzerCellProperties.c)
| getMaxUnusedDigits | [fuzzerResolutions](./fuzzerResolutions.c)
| getUnusedDigits | [fuzzerCellProperties](./fuzzerCellProperties.c)
| setUnusedDigits | [fuzzerCellProperties](./fuzzerCellProperties.c)
| getDirectedEdgeDestination | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| getDirectedEdgeOrigin | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
| getHexagonAreaAvg | [fuzzerResolutions](./fuzzerResolutions.c)
Expand Down
5 changes: 5 additions & 0 deletions src/apps/fuzzers/fuzzerCellProperties.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

typedef struct {
H3Index index;
H3Index mask;
} inputArgs;

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
Expand All @@ -45,6 +46,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
free(out);
}

H3_EXPORT(getUnusedDigits)(args->index);
H3Index maskOut;
H3_EXPORT(setUnusedDigits)(args->index, args->mask, &maskOut);

return 0;
}

Expand Down
3 changes: 3 additions & 0 deletions src/apps/fuzzers/fuzzerResolutions.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
H3Index pentagons[12];
H3_EXPORT(getPentagons)(args->res, pentagons);

H3Index outMask;
H3_EXPORT(getMaxUnusedDigits)(args->res, &outMask);

return 0;
}

Expand Down
106 changes: 106 additions & 0 deletions src/apps/testapps/testIndexDigits.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2025 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @file
* @brief tests index digits get/set and unused digits functions
*
* usage: `testIndexDigits`
*/

#include <math.h>

#include "h3Index.h"
#include "h3api.h"
#include "latLng.h"
#include "test.h"
#include "utility.h"

SUITE(indexDigits) {
TEST(getMaxUnusedDigits) {
// TODO: assert all specific values
H3Index currentMask, previousMask = 0;
for (int res = MAX_H3_RES; res >= 0; res--) {
t_assertSuccess(H3_EXPORT(getMaxUnusedDigits)(res, &currentMask));

if (res != MAX_H3_RES) {
t_assert(currentMask > previousMask,
"mask should get bigger for increasing cell size");
}
previousMask = currentMask;

if (res == MAX_H3_RES) {
t_assert(currentMask == 0, "res 15 has 0 mask");
}
if (res == 14) {
t_assert(currentMask == 7, "res 14 has 7 mask");
}
if (res == 13) {
t_assert(currentMask == (7 | (7 << 3)),
"res 13 has expected mask");
}
if (res == 12) {
t_assert(currentMask == (7 | ((7 | (7 << 3)) << 3)),
"res 12 has expected mask");
}
}
}

TEST(getMaxUnusedDigits_invalid) {
H3Index mask;
t_assert(H3_EXPORT(getMaxUnusedDigits)(-1, &mask) == E_RES_DOMAIN,
"get max unused digits -1");
t_assert(H3_EXPORT(getMaxUnusedDigits)(16, &mask) == E_RES_DOMAIN,
"get max unused digits 16");
}

TEST(getUnusedDigits) {
H3Index h;

for (int expectedDigit = CENTER_DIGIT; expectedDigit < INVALID_DIGIT;
expectedDigit++) {
for (int resCell = 0; resCell <= MAX_H3_RES; resCell++) {
setH3Index(&h, resCell, 0, expectedDigit);

H3Index mask;
H3Index expectedMask;
mask = H3_EXPORT(getUnusedDigits)(h);
t_assertSuccess(
H3_EXPORT(getMaxUnusedDigits)(resCell, &expectedMask));

t_assert(mask == expectedMask,
"mask and expected mask are the same");

if (resCell < MAX_H3_RES) {
H3Index h2;
H3Index mask2 = mask - 1;
t_assertSuccess(H3_EXPORT(setUnusedDigits)(h, mask2, &h2));

H3Index mask3 = H3_EXPORT(getUnusedDigits)(h2);

t_assert(mask3 == mask2,
"retrieved mask is same as expected");
t_assert(h2 < h, "index is logically modified");

H3Index mask4 = mask + 1;
H3Index h3 = 0;
t_assert(
H3_EXPORT(setUnusedDigits)(h, mask4, &h3) == E_DOMAIN,
"index cannot accept larger unused digits");
t_assert(h3 == 0, "index unmodified");
}
}
}
}
}
39 changes: 39 additions & 0 deletions src/h3lib/include/h3api.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,45 @@ DECLSPEC int H3_EXPORT(getResolution)(H3Index h);
DECLSPEC int H3_EXPORT(getBaseCellNumber)(H3Index h);
/** @} */

/** @defgroup getMaxUnusedDigits getMaxUnusedDigits
* Functions for getMaxUnusedDigits
* @{
*/
/** @brief returns the maximum value that can be provided to setUnusedDigits
* for a resolution.
*
* This value is the same as the value that should be set for the index to be
* a valid cell.
*/
DECLSPEC H3Error H3_EXPORT(getMaxUnusedDigits)(int res, H3Index *out);
/** @} */

/** @defgroup getUnusedDigits getUnusedDigits
* Functions for getUnusedDigits
* @{
*/
/** @brief Retrieve the value stored in the unused digits of the H3 index.
*
* For valid cells this will always be the value of getMaxUnusedDigits for
* the cell's resolution.
*/
DECLSPEC H3Index H3_EXPORT(getUnusedDigits)(H3Index h);
/** @} */

/** @defgroup setUnusedDigits setUnusedDigits
* Functions for setUnusedDigits
* @{
*/
/** @brief Creates an index with the unused digits set to another value.
*
* Unless data is the value provided by getMaxUnusedDigits, this will create
* an invalid cell. To create a valid cell again, pass the created index in
* to this function with `data` set to the value given by getMaxUnusedDigits.
*/
DECLSPEC H3Error H3_EXPORT(setUnusedDigits)(H3Index h, H3Index data,
H3Index *out);
/** @} */

/** @defgroup stringToH3 stringToH3
* Functions for stringToH3
* @{
Expand Down
Loading
Loading
0