From de71b07792a0751406e8f83264f08ec78449a15c Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Fri, 3 Nov 2023 17:50:09 -0700 Subject: [PATCH 01/25] Add support for full containment mode in polygonToCells --- src/apps/testapps/testPolygonToCells.c | 10 ++-- .../testapps/testPolygonToCellsExperimental.c | 10 ++-- src/h3lib/include/polygon.h | 15 ++++++ src/h3lib/lib/algos.c | 10 ++-- src/h3lib/lib/polyfill.c | 54 ++++++++++++++----- src/h3lib/lib/polygon.c | 15 ++++++ 6 files changed, 88 insertions(+), 26 deletions(-) diff --git a/src/apps/testapps/testPolygonToCells.c b/src/apps/testapps/testPolygonToCells.c index 22a0cdecf..b35dedd58 100644 --- a/src/apps/testapps/testPolygonToCells.c +++ b/src/apps/testapps/testPolygonToCells.c @@ -461,19 +461,21 @@ SUITE(polygonToCells) { TEST(invalidFlags) { int64_t numHexagons; - for (uint32_t flags = 1; flags <= 32; flags++) { + for (uint32_t flags = 3; flags <= 32; flags++) { t_assert( H3_EXPORT(maxPolygonToCellsSize)( &sfGeoPolygon, 9, flags, &numHexagons) == E_OPTION_INVALID, - "Flags other than 0 are invalid for maxPolygonToCellsSize"); + "Flags other than polyfill modes are invalid for " + "maxPolygonToCellsSize"); } t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - for (uint32_t flags = 1; flags <= 32; flags++) { + for (uint32_t flags = 3; flags <= 32; flags++) { t_assert(H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, flags, hexagons) == E_OPTION_INVALID, - "Flags other than 0 are invalid for polygonToCells"); + "Flags other than polyfill modes are invalid for " + "polygonToCells"); } free(hexagons); } diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index f389456ab..22a825e48 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -413,19 +413,21 @@ SUITE(polygonToCells) { TEST(invalidFlags) { int64_t numHexagons; - for (uint32_t flags = 1; flags <= 32; flags++) { + for (uint32_t flags = 3; flags <= 32; flags++) { t_assert( H3_EXPORT(maxPolygonToCellsSize)( &sfGeoPolygon, 9, flags, &numHexagons) == E_OPTION_INVALID, - "Flags other than 0 are invalid for maxPolygonToCellsSize"); + "Flags other than polyfill modes are invalid for " + "maxPolygonToCellsSize"); } t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - for (uint32_t flags = 1; flags <= 32; flags++) { + for (uint32_t flags = 3; flags <= 32; flags++) { t_assert(H3_EXPORT(polygonToCellsExperimental)( &sfGeoPolygon, 9, flags, hexagons) == E_OPTION_INVALID, - "Flags other than 0 are invalid for polygonToCells"); + "Flags other than polyfill modes are invalid for " + "polygonToCells"); } free(hexagons); } diff --git a/src/h3lib/include/polygon.h b/src/h3lib/include/polygon.h index 387ed034a..3fce0c692 100644 --- a/src/h3lib/include/polygon.h +++ b/src/h3lib/include/polygon.h @@ -40,7 +40,22 @@ /** Macro: Whether a GeoLoop is empty */ #define IS_EMPTY_GEOFENCE(geoloop) geoloop->numVerts == 0 +/** + * Values representing polyfill containment modes, to be used in + * the `flags` bit field. + */ +typedef enum { + CENTER_CONTAINMENT = 0, + FULL_CONTAINMENT = 1, + INTERSECTION = 2 +} ContainmentMode; + +// 1s in the 4 bits defining the polyfill containment mode, 0s elsewhere +#define FLAG_CONTAINMENT_MODE_MASK ((uint32_t)(15)) +#define FLAG_GET_CONTAINMENT_MODE(flags) (flags & FLAG_CONTAINMENT_MODE_MASK) + // Defined directly in polygon.c: +H3Error validatePolygonFlags(uint32_t flags); void bboxesFromGeoPolygon(const GeoPolygon *polygon, BBox *bboxes); bool pointInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, const LatLng *coord); diff --git a/src/h3lib/lib/algos.c b/src/h3lib/lib/algos.c index 79666d843..949988a08 100644 --- a/src/h3lib/lib/algos.c +++ b/src/h3lib/lib/algos.c @@ -774,8 +774,9 @@ H3Error H3_EXPORT(gridRingUnsafe)(H3Index origin, int k, H3Index *out) { */ H3Error H3_EXPORT(maxPolygonToCellsSize)(const GeoPolygon *geoPolygon, int res, uint32_t flags, int64_t *out) { - if (flags != 0) { - return E_OPTION_INVALID; + H3Error flagErr = validatePolygonFlags(flags); + if (flagErr) { + return flagErr; } // Get the bounding box for the GeoJSON-like struct BBox bbox; @@ -890,8 +891,9 @@ H3Error _getEdgeHexagons(const GeoLoop *geoloop, int64_t numHexagons, int res, */ H3Error H3_EXPORT(polygonToCells)(const GeoPolygon *geoPolygon, int res, uint32_t flags, H3Index *out) { - if (flags != 0) { - return E_OPTION_INVALID; + H3Error flagErr = validatePolygonFlags(flags); + if (flagErr) { + return flagErr; } // One of the goals of the polygonToCells algorithm is that two adjacent // polygons with zero overlap have zero overlapping hexagons. That the diff --git a/src/h3lib/lib/polyfill.c b/src/h3lib/lib/polyfill.c index 7ba5a28b5..6411f6972 100644 --- a/src/h3lib/lib/polyfill.c +++ b/src/h3lib/lib/polyfill.c @@ -354,8 +354,9 @@ IterCellsPolygonCompact iterInitPolygonCompact(const GeoPolygon *polygon, return iter; } - if (flags != 0) { - iterErrorPolygonCompact(&iter, E_OPTION_INVALID); + H3Error flagErr = validatePolygonFlags(flags); + if (flagErr) { + iterErrorPolygonCompact(&iter, flagErr); return iter; } @@ -406,23 +407,48 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { iter->_started = true; } + ContainmentMode mode = FLAG_GET_CONTAINMENT_MODE(iter->_flags); + while (cell) { int cellRes = H3_GET_RESOLUTION(cell); // Target res: Do a fine-grained check if (cellRes == iter->_res) { - // Check if the cell is in the polygon - // TODO: Handle other polyfill modes here - LatLng center; - H3Error centerErr = H3_EXPORT(cellToLatLng)(cell, ¢er); - if (NEVER(centerErr != E_SUCCESS)) { - iterErrorPolygonCompact(iter, centerErr); - return; - } - if (pointInsidePolygon(iter->_polygon, iter->_bboxes, ¢er)) { - // Set to next output - iter->cell = cell; - return; + if (mode == CENTER_CONTAINMENT) { + // Check if the cell center is inside the polygon + LatLng center; + H3Error centerErr = H3_EXPORT(cellToLatLng)(cell, ¢er); + if (centerErr != E_SUCCESS) { + iterErrorPolygonCompact(iter, centerErr); + return; + } + if (pointInsidePolygon(iter->_polygon, iter->_bboxes, + ¢er)) { + // Set to next output + iter->cell = cell; + return; + } + } else if (mode == FULL_CONTAINMENT) { + // Check if the cell is fully contained by the polygon + CellBoundary boundary; + H3Error boundaryErr = + H3_EXPORT(cellToBoundary)(cell, &boundary); + if (boundaryErr != E_SUCCESS) { + iterErrorPolygonCompact(iter, boundaryErr); + return; + } + BBox bbox; + H3Error bboxErr = cellToBBox(cell, &bbox, false); + if (bboxErr) { + iterErrorPolygonCompact(iter, bboxErr); + return; + } + if (cellBoundaryInsidePolygon(iter->_polygon, iter->_bboxes, + &boundary, &bbox)) { + // Set to next output + iter->cell = cell; + return; + } } } diff --git a/src/h3lib/lib/polygon.c b/src/h3lib/lib/polygon.c index a92fecfba..e1ec4a37d 100644 --- a/src/h3lib/lib/polygon.c +++ b/src/h3lib/lib/polygon.c @@ -42,6 +42,21 @@ #undef ITERATE #undef IS_EMPTY +/** + * Whether the flags for the polyfill operation are valid + * TODO: Move to polyfill.c when the old algo is removed + * @param flags Flags to validate + * @return Whether the flags are valid + */ +H3Error validatePolygonFlags(uint32_t flags) { + if (flags & (~FLAG_CONTAINMENT_MODE_MASK) || + !(FLAG_GET_CONTAINMENT_MODE(flags) == CENTER_CONTAINMENT || + FLAG_GET_CONTAINMENT_MODE(flags) == FULL_CONTAINMENT)) { + return E_OPTION_INVALID; + } + return E_SUCCESS; +} + /** * Create a bounding box from a GeoPolygon * @param polygon Input GeoPolygon From 45255d55a42cff678b6bb39bc6b4ab85719dca06 Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Fri, 3 Nov 2023 17:59:33 -0700 Subject: [PATCH 02/25] Add tests --- .../testapps/testPolygonToCellsExperimental.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 22a825e48..2baa7c587 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -22,6 +22,7 @@ #include "h3Index.h" #include "latLng.h" #include "polyfill.h" +#include "polygon.h" #include "test.h" #include "utility.h" @@ -172,6 +173,21 @@ SUITE(polygonToCells) { free(hexagons); } + TEST(polygonToCellsFullContainment) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &sfGeoPolygon, 9, FULL_CONTAINMENT, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &sfGeoPolygon, 9, FULL_CONTAINMENT, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 1175, + "got expected polygonToCells size (full containment mode)"); + free(hexagons); + } + TEST(polygonToCellsHole) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&holeGeoPolygon, 9, 0, @@ -187,6 +203,22 @@ SUITE(polygonToCells) { free(hexagons); } + TEST(polygonToCellsHoleFullContainment) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &holeGeoPolygon, 9, FULL_CONTAINMENT, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &holeGeoPolygon, 9, FULL_CONTAINMENT, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert( + actualNumIndexes == 1118, + "got expected polygonToCells size (hole, full containment mode)"); + free(hexagons); + } + TEST(polygonToCellsEmpty) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&emptyGeoPolygon, 9, 0, From 61d3e5bcac518db1945311635b6a3bf662417cd5 Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Sat, 4 Nov 2023 14:49:45 -0700 Subject: [PATCH 03/25] Implement OVERLAPPING mode, update tests to use mode flags --- scripts/make_countries.js | 17 ++- .../benchmarkPolygonToCellsExperimental.c | 19 ++- src/apps/testapps/testH3Memory.c | 13 +- src/apps/testapps/testPolygonInternal.c | 24 ++-- .../testapps/testPolygonToCellsExperimental.c | 118 ++++++++++++------ .../testPolygonToCellsReportedExperimental.c | 44 ++++--- src/h3lib/include/polygon.h | 11 +- src/h3lib/lib/polyfill.c | 20 ++- src/h3lib/lib/polygon.c | 36 +++++- 9 files changed, 207 insertions(+), 95 deletions(-) diff --git a/scripts/make_countries.js b/scripts/make_countries.js index 4e8a495d4..42c0a3974 100644 --- a/scripts/make_countries.js +++ b/scripts/make_countries.js @@ -131,6 +131,7 @@ async function makeCountries(sourceUrl, targetPath) { #include "h3api.h" #include "mathExtensions.h" #include "polyfill.h" +#include "polygon.h" const GeoPolygon COUNTRIES[${polygons.length}] = {${ polygons.map((poly, i) => formatGeoPolygon(poly, names[i])).join(',') @@ -149,13 +150,25 @@ for (int res = 0; res < MAX_RES + 1; res++) { BENCHMARK(polygonToCells_AllCountries1, 5, { for (int index = 0; index < ${polygons.length}; index++) { - H3_EXPORT(polygonToCells)(&COUNTRIES[index], res, 0, hexagons); + H3_EXPORT(polygonToCells)(&COUNTRIES[index], res, CENTER_CONTAINMENT, hexagons); } }); BENCHMARK(polygonToCells_AllCountries2, 5, { for (int index = 0; index < ${polygons.length}; index++) { - H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, 0, hexagons); + H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, CENTER_CONTAINMENT, hexagons); + } + }); + + BENCHMARK(polygonToCells_AllCountries3, 5, { + for (int index = 0; index < ${polygons.length}; index++) { + H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, FULL_CONTAINMENT, hexagons); + } + }); + + BENCHMARK(polygonToCells_AllCountries4, 5, { + for (int index = 0; index < ${polygons.length}; index++) { + H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, OVERLAPPING, hexagons); } }); diff --git a/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c b/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c index b0837c7fc..13c01cebd 100644 --- a/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c +++ b/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c @@ -17,6 +17,7 @@ #include "benchmark.h" #include "h3api.h" #include "polyfill.h" +#include "polygon.h" // Fixtures LatLng sfVerts[] = { @@ -123,23 +124,29 @@ int64_t numHexagons; H3Index *hexagons; BENCHMARK(polygonToCellsSF, 500, { - H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0, &numHexagons); + H3_EXPORT(maxPolygonToCellsSize) + (&sfGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); - H3_EXPORT(polygonToCellsExperimental)(&sfGeoPolygon, 9, 0, hexagons); + H3_EXPORT(polygonToCellsExperimental) + (&sfGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); free(hexagons); }); BENCHMARK(polygonToCellsAlameda, 500, { - H3_EXPORT(maxPolygonToCellsSize)(&alamedaGeoPolygon, 9, 0, &numHexagons); + H3_EXPORT(maxPolygonToCellsSize) + (&alamedaGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); - H3_EXPORT(polygonToCellsExperimental)(&alamedaGeoPolygon, 9, 0, hexagons); + H3_EXPORT(polygonToCellsExperimental) + (&alamedaGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); free(hexagons); }); BENCHMARK(polygonToCellsSouthernExpansion, 10, { - H3_EXPORT(maxPolygonToCellsSize)(&southernGeoPolygon, 9, 0, &numHexagons); + H3_EXPORT(maxPolygonToCellsSize) + (&southernGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); - H3_EXPORT(polygonToCellsExperimental)(&southernGeoPolygon, 9, 0, hexagons); + H3_EXPORT(polygonToCellsExperimental) + (&southernGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); free(hexagons); }); diff --git a/src/apps/testapps/testH3Memory.c b/src/apps/testapps/testH3Memory.c index 96627b543..f4e9ec4af 100644 --- a/src/apps/testapps/testH3Memory.c +++ b/src/apps/testapps/testH3Memory.c @@ -26,6 +26,7 @@ #include "h3api.h" #include "latLng.h" #include "polyfill.h" +#include "polygon.h" #include "test.h" #include "utility.h" @@ -230,22 +231,22 @@ SUITE(h3Memory) { sfGeoPolygon.numHoles = 0; int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &sfGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); resetMemoryCounters(0); failAlloc = true; - H3Error err = H3_EXPORT(polygonToCellsExperimental)(&sfGeoPolygon, 9, 0, - hexagons); + H3Error err = H3_EXPORT(polygonToCellsExperimental)( + &sfGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); t_assert(err == E_MEMORY_ALLOC, "polygonToCellsExperimental failed (1)"); t_assert(actualAllocCalls == 1, "alloc called once"); t_assert(actualFreeCalls == 0, "free not called"); resetMemoryCounters(1); - err = H3_EXPORT(polygonToCellsExperimental)(&sfGeoPolygon, 9, 0, - hexagons); + err = H3_EXPORT(polygonToCellsExperimental)( + &sfGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); t_assert(err == E_SUCCESS, "polygonToCellsExperimental succeeded (1)"); t_assert(actualAllocCalls == 1, "alloc called one time"); t_assert(actualFreeCalls == 1, "free called one time"); diff --git a/src/apps/testapps/testPolygonInternal.c b/src/apps/testapps/testPolygonInternal.c index 4b0f80286..42d19d14a 100644 --- a/src/apps/testapps/testPolygonInternal.c +++ b/src/apps/testapps/testPolygonInternal.c @@ -626,55 +626,55 @@ SUITE(polygonInternal) { H3_EXPORT(destroyLinkedMultiPolygon)(&polygon); } - TEST(lineIntersectsLine) { + TEST(lineCrossesLine) { LatLng lines1[4] = {{0, 0}, {1, 1}, {0, 1}, {1, 0}}; t_assert( - lineIntersectsLine(&lines1[0], &lines1[1], &lines1[2], &lines1[3]), + lineCrossesLine(&lines1[0], &lines1[1], &lines1[2], &lines1[3]), "diagonal intersection"); LatLng lines2[4] = {{1, 1}, {0, 0}, {1, 0}, {0, 1}}; t_assert( - lineIntersectsLine(&lines2[0], &lines2[1], &lines2[2], &lines2[3]), + lineCrossesLine(&lines2[0], &lines2[1], &lines2[2], &lines2[3]), "diagonal intersection, reverse vertexes"); LatLng lines3[4] = {{0.5, 0}, {0.5, 1}, {0, 0.5}, {1, 0.5}}; t_assert( - lineIntersectsLine(&lines3[0], &lines3[1], &lines3[2], &lines3[3]), + lineCrossesLine(&lines3[0], &lines3[1], &lines3[2], &lines3[3]), "horizontal/vertical intersection"); LatLng lines4[4] = {{0.5, 1}, {0.5, 0}, {1, 0.5}, {0, 0.5}}; t_assert( - lineIntersectsLine(&lines4[0], &lines4[1], &lines4[2], &lines4[3]), + lineCrossesLine(&lines4[0], &lines4[1], &lines4[2], &lines4[3]), "horizontal/vertical intersection, reverse vertexes"); LatLng lines5[4] = {{0, 0}, {0.4, 0.4}, {0, 1}, {1, 0}}; t_assert( - !lineIntersectsLine(&lines5[0], &lines5[1], &lines5[2], &lines5[3]), + !lineCrossesLine(&lines5[0], &lines5[1], &lines5[2], &lines5[3]), "diagonal non-intersection, below"); LatLng lines6[4] = {{0.6, 0.6}, {1, 1}, {0, 1}, {1, 0}}; t_assert( - !lineIntersectsLine(&lines6[0], &lines6[1], &lines6[2], &lines6[3]), + !lineCrossesLine(&lines6[0], &lines6[1], &lines6[2], &lines6[3]), "diagonal non-intersection, above"); LatLng lines7[4] = {{0.5, 0}, {0.5, 1}, {0, 0.5}, {0.4, 0.5}}; t_assert( - !lineIntersectsLine(&lines7[0], &lines7[1], &lines7[2], &lines7[3]), + !lineCrossesLine(&lines7[0], &lines7[1], &lines7[2], &lines7[3]), "horizontal/vertical non-intersection, below"); LatLng lines8[4] = {{0.5, 0}, {0.5, 1}, {0.6, 0.5}, {1, 0.5}}; t_assert( - !lineIntersectsLine(&lines8[0], &lines8[1], &lines8[2], &lines8[3]), + !lineCrossesLine(&lines8[0], &lines8[1], &lines8[2], &lines8[3]), "horizontal/vertical non-intersection, above"); LatLng lines9[4] = {{0.5, 0}, {0.5, 0.4}, {0, 0.5}, {1, 0.5}}; t_assert( - !lineIntersectsLine(&lines9[0], &lines9[1], &lines9[2], &lines9[3]), + !lineCrossesLine(&lines9[0], &lines9[1], &lines9[2], &lines9[3]), "horizontal/vertical non-intersection, left"); LatLng lines10[4] = {{0.5, 0.6}, {0.5, 1}, {0, 0.5}, {1, 0.5}}; - t_assert(!lineIntersectsLine(&lines10[0], &lines10[1], &lines10[2], - &lines10[3]), + t_assert(!lineCrossesLine(&lines10[0], &lines10[1], &lines10[2], + &lines10[3]), "horizontal/vertical non-intersection, right"); } diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 2baa7c587..1435c8d51 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -100,7 +100,7 @@ static void fillIndex_assertions(H3Index h) { H3Index *polygonToCellsOut = calloc(polygonToCellsSize, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &polygon, nextRes, 0, polygonToCellsOut)); + &polygon, nextRes, CENTER_CONTAINMENT, polygonToCellsOut)); int64_t polygonToCellsCount = countNonNullIndexes(polygonToCellsOut, polygonToCellsSize); @@ -161,12 +161,12 @@ SUITE(polygonToCells) { TEST(polygonToCells) { int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &sfGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&sfGeoPolygon, 9, - 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &sfGeoPolygon, 9, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1253, "got expected polygonToCells size"); @@ -188,14 +188,29 @@ SUITE(polygonToCells) { free(hexagons); } + TEST(polygonToCellsOverlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &sfGeoPolygon, 9, OVERLAPPING, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &sfGeoPolygon, 9, OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 1334, + "got expected polygonToCells size (overlapping mode)"); + free(hexagons); + } + TEST(polygonToCellsHole) { int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&holeGeoPolygon, 9, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &holeGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&holeGeoPolygon, - 9, 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &holeGeoPolygon, 9, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1214, @@ -219,14 +234,29 @@ SUITE(polygonToCells) { free(hexagons); } + TEST(polygonToCellsHoleOverlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &holeGeoPolygon, 9, OVERLAPPING, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &holeGeoPolygon, 9, OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 1311, + "got expected polygonToCells size (hole, overlapping mode)"); + free(hexagons); + } + TEST(polygonToCellsEmpty) { int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&emptyGeoPolygon, 9, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &emptyGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&emptyGeoPolygon, - 9, 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &emptyGeoPolygon, 9, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 0, @@ -255,15 +285,27 @@ SUITE(polygonToCells) { someHexagon.numHoles = 0; int64_t numHexagons; - t_assertSuccess( - H3_EXPORT(maxPolygonToCellsSize)(&someHexagon, 9, 0, &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &someHexagon, 9, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&someHexagon, 9, - 0, hexagons)); - int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + int64_t actualNumIndexes; + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &someHexagon, 9, CENTER_CONTAINMENT, hexagons)); + actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + t_assert(actualNumIndexes == 1, + "got expected polygonToCells size for center containment (1)"); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &someHexagon, 9, FULL_CONTAINMENT, hexagons)); + actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + t_assert(actualNumIndexes == 1, + "got expected polygonToCells size for full containment (1)"); + + // TODO: OVERLAPPING yields 7 cells, presumably due to FPE in the + // various cell boundaries - t_assert(actualNumIndexes == 1, "got expected polygonToCells size (1)"); free(hexagons); free(verts); } @@ -304,11 +346,11 @@ SUITE(polygonToCells) { expectedSize = 4228; int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &primeMeridianGeoPolygon, 7, 0, &numHexagons)); + &primeMeridianGeoPolygon, 7, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &primeMeridianGeoPolygon, 7, 0, hexagons)); + &primeMeridianGeoPolygon, 7, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == expectedSize, @@ -319,11 +361,11 @@ SUITE(polygonToCells) { // differences in hex size and grid offset between the two cases expectedSize = 4238; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &transMeridianGeoPolygon, 7, 0, &numHexagons)); + &transMeridianGeoPolygon, 7, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagonsTM = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &transMeridianGeoPolygon, 7, 0, hexagonsTM)); + &transMeridianGeoPolygon, 7, CENTER_CONTAINMENT, hexagonsTM)); actualNumIndexes = countNonNullIndexes(hexagonsTM, numHexagons); t_assert(actualNumIndexes == expectedSize, @@ -332,21 +374,23 @@ SUITE(polygonToCells) { // Transmeridian filled hole case -- only needed for calculating hole // size t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &transMeridianFilledHoleGeoPolygon, 7, 0, &numHexagons)); + &transMeridianFilledHoleGeoPolygon, 7, CENTER_CONTAINMENT, + &numHexagons)); H3Index *hexagonsTMFH = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &transMeridianFilledHoleGeoPolygon, 7, 0, hexagonsTMFH)); + &transMeridianFilledHoleGeoPolygon, 7, CENTER_CONTAINMENT, + hexagonsTMFH)); int64_t actualNumHoleIndexes = countNonNullIndexes(hexagonsTMFH, numHexagons); // Transmeridian hole case t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &transMeridianHoleGeoPolygon, 7, 0, &numHexagons)); + &transMeridianHoleGeoPolygon, 7, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagonsTMH = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &transMeridianHoleGeoPolygon, 7, 0, hexagonsTMH)); + &transMeridianHoleGeoPolygon, 7, CENTER_CONTAINMENT, hexagonsTMH)); actualNumIndexes = countNonNullIndexes(hexagonsTMH, numHexagons); t_assert(actualNumIndexes == expectedSize - actualNumHoleIndexes, @@ -369,12 +413,12 @@ SUITE(polygonToCells) { GeoPolygon polygon = {.geoloop = geoloop, .numHoles = 0}; int64_t numHexagons; - t_assertSuccess( - H3_EXPORT(maxPolygonToCellsSize)(&polygon, 4, 0, &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &polygon, 4, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess( - H3_EXPORT(polygonToCellsExperimental)(&polygon, 4, 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &polygon, 4, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); @@ -421,12 +465,12 @@ SUITE(polygonToCells) { polygon.numHoles = 0; int64_t numHexagons; - t_assertSuccess( - H3_EXPORT(maxPolygonToCellsSize)(&polygon, 9, 0, &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &polygon, 9, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess( - H3_EXPORT(polygonToCellsExperimental)(&polygon, 9, 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &polygon, 9, CENTER_CONTAINMENT, hexagons)); int found = 0; int numPentagons = 0; @@ -452,8 +496,8 @@ SUITE(polygonToCells) { "Flags other than polyfill modes are invalid for " "maxPolygonToCellsSize"); } - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &sfGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); for (uint32_t flags = 3; flags <= 32; flags++) { t_assert(H3_EXPORT(polygonToCellsExperimental)( diff --git a/src/apps/testapps/testPolygonToCellsReportedExperimental.c b/src/apps/testapps/testPolygonToCellsReportedExperimental.c index 58e688504..54aee4136 100644 --- a/src/apps/testapps/testPolygonToCellsReportedExperimental.c +++ b/src/apps/testapps/testPolygonToCellsReportedExperimental.c @@ -21,6 +21,7 @@ #include "h3Index.h" #include "latLng.h" #include "polyfill.h" +#include "polygon.h" #include "test.h" #include "utility.h" @@ -42,23 +43,26 @@ SUITE(polygonToCells_reported) { for (int res = 0; res < 3; res++) { int64_t polygonToCellsSize; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &worldGeoPolygon, res, 0, &polygonToCellsSize)); + &worldGeoPolygon, res, CENTER_CONTAINMENT, + &polygonToCellsSize)); H3Index *polygonToCellsOut = calloc(polygonToCellsSize, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &worldGeoPolygon, res, 0, polygonToCellsOut)); + &worldGeoPolygon, res, CENTER_CONTAINMENT, polygonToCellsOut)); int64_t actualNumIndexes = countNonNullIndexes(polygonToCellsOut, polygonToCellsSize); int64_t polygonToCellsSize2; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &worldGeoPolygon2, res, 0, &polygonToCellsSize2)); + &worldGeoPolygon2, res, CENTER_CONTAINMENT, + &polygonToCellsSize2)); H3Index *polygonToCellsOut2 = calloc(polygonToCellsSize2, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &worldGeoPolygon2, res, 0, polygonToCellsOut2)); + &worldGeoPolygon2, res, CENTER_CONTAINMENT, + polygonToCellsOut2)); int64_t actualNumIndexes2 = countNonNullIndexes(polygonToCellsOut2, polygonToCellsSize2); @@ -105,12 +109,12 @@ SUITE(polygonToCells_reported) { int res = 7; int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&testPolygon, res, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &testPolygon, res, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&testPolygon, res, - 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &testPolygon, res, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 4499, @@ -134,12 +138,12 @@ SUITE(polygonToCells_reported) { int res = 7; int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&testPolygon, res, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &testPolygon, res, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&testPolygon, res, - 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &testPolygon, res, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 4609, @@ -160,12 +164,12 @@ SUITE(polygonToCells_reported) { int res = 13; int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&testPolygon, res, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &testPolygon, res, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&testPolygon, res, - 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &testPolygon, res, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 4353, "got expected polygonToCells size"); @@ -193,12 +197,12 @@ SUITE(polygonToCells_reported) { int res = 5; int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&testPolygon, res, 0, - &numHexagons)); + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &testPolygon, res, CENTER_CONTAINMENT, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&testPolygon, res, - 0, hexagons)); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &testPolygon, res, CENTER_CONTAINMENT, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 8, "got expected polygonToCells size"); diff --git a/src/h3lib/include/polygon.h b/src/h3lib/include/polygon.h index 3fce0c692..3a07e6f7e 100644 --- a/src/h3lib/include/polygon.h +++ b/src/h3lib/include/polygon.h @@ -47,7 +47,8 @@ typedef enum { CENTER_CONTAINMENT = 0, FULL_CONTAINMENT = 1, - INTERSECTION = 2 + OVERLAPPING = 2, + INVALID_CONTAINMENT = 3 } ContainmentMode; // 1s in the 4 bits defining the polyfill containment mode, 0s elsewhere @@ -62,11 +63,15 @@ bool pointInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, bool cellBoundaryInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, const CellBoundary *boundary, const BBox *boundaryBBox); +bool cellBoundaryCrossesPolygon(const GeoPolygon *geoPolygon, + const BBox *bboxes, + const CellBoundary *boundary, + const BBox *boundaryBBox); bool cellBoundaryCrossesGeoLoop(const GeoLoop *geoloop, const BBox *loopBBox, const CellBoundary *boundary, const BBox *boundaryBBox); -bool lineIntersectsLine(const LatLng *a1, const LatLng *a2, const LatLng *b1, - const LatLng *b2); +bool lineCrossesLine(const LatLng *a1, const LatLng *a2, const LatLng *b1, + const LatLng *b2); // The following functions are created via macro in polygonAlgos.h, // so their signatures are documented here: diff --git a/src/h3lib/lib/polyfill.c b/src/h3lib/lib/polyfill.c index 6411f6972..8a957f9f6 100644 --- a/src/h3lib/lib/polyfill.c +++ b/src/h3lib/lib/polyfill.c @@ -414,7 +414,7 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { // Target res: Do a fine-grained check if (cellRes == iter->_res) { - if (mode == CENTER_CONTAINMENT) { + if (mode == CENTER_CONTAINMENT || mode == OVERLAPPING) { // Check if the cell center is inside the polygon LatLng center; H3Error centerErr = H3_EXPORT(cellToLatLng)(cell, ¢er); @@ -428,8 +428,8 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { iter->cell = cell; return; } - } else if (mode == FULL_CONTAINMENT) { - // Check if the cell is fully contained by the polygon + } + if (mode == FULL_CONTAINMENT || mode == OVERLAPPING) { CellBoundary boundary; H3Error boundaryErr = H3_EXPORT(cellToBoundary)(cell, &boundary); @@ -443,12 +443,24 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { iterErrorPolygonCompact(iter, bboxErr); return; } - if (cellBoundaryInsidePolygon(iter->_polygon, iter->_bboxes, + // Check if the cell is fully contained by the polygon + if (mode == FULL_CONTAINMENT && + cellBoundaryInsidePolygon(iter->_polygon, iter->_bboxes, &boundary, &bbox)) { // Set to next output iter->cell = cell; return; } + // For overlap, we've already checked for center point inclusion + // above; if that failed, we only need to check for line + // intersection + else if (mode == OVERLAPPING && + cellBoundaryCrossesPolygon( + iter->_polygon, iter->_bboxes, &boundary, &bbox)) { + // Set to next output + iter->cell = cell; + return; + } } } diff --git a/src/h3lib/lib/polygon.c b/src/h3lib/lib/polygon.c index e1ec4a37d..3685d0b7a 100644 --- a/src/h3lib/lib/polygon.c +++ b/src/h3lib/lib/polygon.c @@ -50,8 +50,7 @@ */ H3Error validatePolygonFlags(uint32_t flags) { if (flags & (~FLAG_CONTAINMENT_MODE_MASK) || - !(FLAG_GET_CONTAINMENT_MODE(flags) == CENTER_CONTAINMENT || - FLAG_GET_CONTAINMENT_MODE(flags) == FULL_CONTAINMENT)) { + FLAG_GET_CONTAINMENT_MODE(flags) >= INVALID_CONTAINMENT) { return E_OPTION_INVALID; } return E_SUCCESS; @@ -130,6 +129,33 @@ bool cellBoundaryInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, return true; } +/** + * Whether any part of a cell boundary crosses a polygon. Crossing in this case + * means whether any line segments intersect; it does not include containment. + * @param geoPolygon The polygon to test + * @param bboxes The bboxes for the main geoloop and each of its holes + * @param boundary The cell boundary to test + * @return Whether the cell boundary is contained + */ +bool cellBoundaryCrossesPolygon(const GeoPolygon *geoPolygon, + const BBox *bboxes, + const CellBoundary *boundary, + const BBox *boundaryBBox) { + // Check for line intersections with outer loop + if (cellBoundaryCrossesGeoLoop(&(geoPolygon->geoloop), &bboxes[0], boundary, + boundaryBBox)) { + return true; + } + // Check for line intersections with any hole + for (int i = 0; i < geoPolygon->numHoles; i++) { + if (cellBoundaryCrossesGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1], + boundary, boundaryBBox)) { + return true; + } + } + return false; +} + /** * Whether a cell boundary crosses a geo loop. Crossing in this case means * whether any line segments intersect; it does not include containment. @@ -182,7 +208,7 @@ bool cellBoundaryCrossesGeoLoop(const GeoLoop *geoloop, const BBox *loopBBox, } for (int j = 0; j < normalBoundary.numVerts; j++) { - if (lineIntersectsLine( + if (lineCrossesLine( &loop1, &loop2, &normalBoundary.verts[j], &normalBoundary.verts[(j + 1) % normalBoundary.numVerts])) { return true; @@ -202,8 +228,8 @@ bool cellBoundaryCrossesGeoLoop(const GeoLoop *geoloop, const BBox *loopBBox, * @param b2 End of line B * @return Whether the lines intersect */ -bool lineIntersectsLine(const LatLng *a1, const LatLng *a2, const LatLng *b1, - const LatLng *b2) { +bool lineCrossesLine(const LatLng *a1, const LatLng *a2, const LatLng *b1, + const LatLng *b2) { double denom = ((b2->lng - b1->lng) * (a2->lat - a1->lat) - (b2->lat - b1->lat) * (a2->lng - a1->lng)); if (!denom) return false; From 585f4974bd85a840746e9d030fc8ecaa9a0de6af Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Mon, 6 Nov 2023 10:25:46 -0800 Subject: [PATCH 04/25] Rename containment enum values --- scripts/make_countries.js | 8 +-- .../benchmarkPolygonToCellsExperimental.c | 12 ++-- src/apps/testapps/testH3Memory.c | 6 +- .../testapps/testPolygonToCellsExperimental.c | 66 +++++++++---------- .../testPolygonToCellsReportedExperimental.c | 24 +++---- src/h3lib/include/polygon.h | 8 +-- src/h3lib/lib/polyfill.c | 8 +-- src/h3lib/lib/polygon.c | 2 +- 8 files changed, 67 insertions(+), 67 deletions(-) diff --git a/scripts/make_countries.js b/scripts/make_countries.js index 42c0a3974..8e810dff3 100644 --- a/scripts/make_countries.js +++ b/scripts/make_countries.js @@ -150,25 +150,25 @@ for (int res = 0; res < MAX_RES + 1; res++) { BENCHMARK(polygonToCells_AllCountries1, 5, { for (int index = 0; index < ${polygons.length}; index++) { - H3_EXPORT(polygonToCells)(&COUNTRIES[index], res, CENTER_CONTAINMENT, hexagons); + H3_EXPORT(polygonToCells)(&COUNTRIES[index], res, CONTAINMENT_CENTER, hexagons); } }); BENCHMARK(polygonToCells_AllCountries2, 5, { for (int index = 0; index < ${polygons.length}; index++) { - H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, CENTER_CONTAINMENT, hexagons); + H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, CONTAINMENT_CENTER, hexagons); } }); BENCHMARK(polygonToCells_AllCountries3, 5, { for (int index = 0; index < ${polygons.length}; index++) { - H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, FULL_CONTAINMENT, hexagons); + H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, CONTAINMENT_FULL, hexagons); } }); BENCHMARK(polygonToCells_AllCountries4, 5, { for (int index = 0; index < ${polygons.length}; index++) { - H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, OVERLAPPING, hexagons); + H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, CONTAINMENT_OVERLAPPING, hexagons); } }); diff --git a/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c b/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c index 13c01cebd..b475cd783 100644 --- a/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c +++ b/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c @@ -125,28 +125,28 @@ H3Index *hexagons; BENCHMARK(polygonToCellsSF, 500, { H3_EXPORT(maxPolygonToCellsSize) - (&sfGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons); + (&sfGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); H3_EXPORT(polygonToCellsExperimental) - (&sfGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); + (&sfGeoPolygon, 9, CONTAINMENT_CENTER, hexagons); free(hexagons); }); BENCHMARK(polygonToCellsAlameda, 500, { H3_EXPORT(maxPolygonToCellsSize) - (&alamedaGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons); + (&alamedaGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); H3_EXPORT(polygonToCellsExperimental) - (&alamedaGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); + (&alamedaGeoPolygon, 9, CONTAINMENT_CENTER, hexagons); free(hexagons); }); BENCHMARK(polygonToCellsSouthernExpansion, 10, { H3_EXPORT(maxPolygonToCellsSize) - (&southernGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons); + (&southernGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); H3_EXPORT(polygonToCellsExperimental) - (&southernGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); + (&southernGeoPolygon, 9, CONTAINMENT_CENTER, hexagons); free(hexagons); }); diff --git a/src/apps/testapps/testH3Memory.c b/src/apps/testapps/testH3Memory.c index f4e9ec4af..54a7c688e 100644 --- a/src/apps/testapps/testH3Memory.c +++ b/src/apps/testapps/testH3Memory.c @@ -232,13 +232,13 @@ SUITE(h3Memory) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &sfGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); + &sfGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); resetMemoryCounters(0); failAlloc = true; H3Error err = H3_EXPORT(polygonToCellsExperimental)( - &sfGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); + &sfGeoPolygon, 9, CONTAINMENT_CENTER, hexagons); t_assert(err == E_MEMORY_ALLOC, "polygonToCellsExperimental failed (1)"); t_assert(actualAllocCalls == 1, "alloc called once"); @@ -246,7 +246,7 @@ SUITE(h3Memory) { resetMemoryCounters(1); err = H3_EXPORT(polygonToCellsExperimental)( - &sfGeoPolygon, 9, CENTER_CONTAINMENT, hexagons); + &sfGeoPolygon, 9, CONTAINMENT_CENTER, hexagons); t_assert(err == E_SUCCESS, "polygonToCellsExperimental succeeded (1)"); t_assert(actualAllocCalls == 1, "alloc called one time"); t_assert(actualFreeCalls == 1, "free called one time"); diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 1435c8d51..cf359a4ce 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -100,7 +100,7 @@ static void fillIndex_assertions(H3Index h) { H3Index *polygonToCellsOut = calloc(polygonToCellsSize, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &polygon, nextRes, CENTER_CONTAINMENT, polygonToCellsOut)); + &polygon, nextRes, CONTAINMENT_CENTER, polygonToCellsOut)); int64_t polygonToCellsCount = countNonNullIndexes(polygonToCellsOut, polygonToCellsSize); @@ -162,11 +162,11 @@ SUITE(polygonToCells) { TEST(polygonToCells) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &sfGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); + &sfGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &sfGeoPolygon, 9, CENTER_CONTAINMENT, hexagons)); + &sfGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1253, "got expected polygonToCells size"); @@ -176,11 +176,11 @@ SUITE(polygonToCells) { TEST(polygonToCellsFullContainment) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &sfGeoPolygon, 9, FULL_CONTAINMENT, &numHexagons)); + &sfGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &sfGeoPolygon, 9, FULL_CONTAINMENT, hexagons)); + &sfGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1175, @@ -191,11 +191,11 @@ SUITE(polygonToCells) { TEST(polygonToCellsOverlapping) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &sfGeoPolygon, 9, OVERLAPPING, &numHexagons)); + &sfGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &sfGeoPolygon, 9, OVERLAPPING, hexagons)); + &sfGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1334, @@ -206,11 +206,11 @@ SUITE(polygonToCells) { TEST(polygonToCellsHole) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &holeGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); + &holeGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &holeGeoPolygon, 9, CENTER_CONTAINMENT, hexagons)); + &holeGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1214, @@ -221,11 +221,11 @@ SUITE(polygonToCells) { TEST(polygonToCellsHoleFullContainment) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &holeGeoPolygon, 9, FULL_CONTAINMENT, &numHexagons)); + &holeGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &holeGeoPolygon, 9, FULL_CONTAINMENT, hexagons)); + &holeGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert( @@ -237,11 +237,11 @@ SUITE(polygonToCells) { TEST(polygonToCellsHoleOverlapping) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &holeGeoPolygon, 9, OVERLAPPING, &numHexagons)); + &holeGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &holeGeoPolygon, 9, OVERLAPPING, hexagons)); + &holeGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1311, @@ -252,11 +252,11 @@ SUITE(polygonToCells) { TEST(polygonToCellsEmpty) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &emptyGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); + &emptyGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &emptyGeoPolygon, 9, CENTER_CONTAINMENT, hexagons)); + &emptyGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 0, @@ -286,25 +286,25 @@ SUITE(polygonToCells) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &someHexagon, 9, CENTER_CONTAINMENT, &numHexagons)); + &someHexagon, 9, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); int64_t actualNumIndexes; t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &someHexagon, 9, CENTER_CONTAINMENT, hexagons)); + &someHexagon, 9, CONTAINMENT_CENTER, hexagons)); actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1, "got expected polygonToCells size for center containment (1)"); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &someHexagon, 9, FULL_CONTAINMENT, hexagons)); + &someHexagon, 9, CONTAINMENT_FULL, hexagons)); actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 1, "got expected polygonToCells size for full containment (1)"); - // TODO: OVERLAPPING yields 7 cells, presumably due to FPE in the - // various cell boundaries + // TODO: CONTAINMENT_OVERLAPPING yields 7 cells, presumably due to FPE + // in the various cell boundaries free(hexagons); free(verts); @@ -346,11 +346,11 @@ SUITE(polygonToCells) { expectedSize = 4228; int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &primeMeridianGeoPolygon, 7, CENTER_CONTAINMENT, &numHexagons)); + &primeMeridianGeoPolygon, 7, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &primeMeridianGeoPolygon, 7, CENTER_CONTAINMENT, hexagons)); + &primeMeridianGeoPolygon, 7, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == expectedSize, @@ -361,11 +361,11 @@ SUITE(polygonToCells) { // differences in hex size and grid offset between the two cases expectedSize = 4238; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &transMeridianGeoPolygon, 7, CENTER_CONTAINMENT, &numHexagons)); + &transMeridianGeoPolygon, 7, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagonsTM = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &transMeridianGeoPolygon, 7, CENTER_CONTAINMENT, hexagonsTM)); + &transMeridianGeoPolygon, 7, CONTAINMENT_CENTER, hexagonsTM)); actualNumIndexes = countNonNullIndexes(hexagonsTM, numHexagons); t_assert(actualNumIndexes == expectedSize, @@ -374,23 +374,23 @@ SUITE(polygonToCells) { // Transmeridian filled hole case -- only needed for calculating hole // size t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &transMeridianFilledHoleGeoPolygon, 7, CENTER_CONTAINMENT, + &transMeridianFilledHoleGeoPolygon, 7, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagonsTMFH = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &transMeridianFilledHoleGeoPolygon, 7, CENTER_CONTAINMENT, + &transMeridianFilledHoleGeoPolygon, 7, CONTAINMENT_CENTER, hexagonsTMFH)); int64_t actualNumHoleIndexes = countNonNullIndexes(hexagonsTMFH, numHexagons); // Transmeridian hole case t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &transMeridianHoleGeoPolygon, 7, CENTER_CONTAINMENT, &numHexagons)); + &transMeridianHoleGeoPolygon, 7, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagonsTMH = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &transMeridianHoleGeoPolygon, 7, CENTER_CONTAINMENT, hexagonsTMH)); + &transMeridianHoleGeoPolygon, 7, CONTAINMENT_CENTER, hexagonsTMH)); actualNumIndexes = countNonNullIndexes(hexagonsTMH, numHexagons); t_assert(actualNumIndexes == expectedSize - actualNumHoleIndexes, @@ -414,11 +414,11 @@ SUITE(polygonToCells) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &polygon, 4, CENTER_CONTAINMENT, &numHexagons)); + &polygon, 4, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &polygon, 4, CENTER_CONTAINMENT, hexagons)); + &polygon, 4, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); @@ -466,11 +466,11 @@ SUITE(polygonToCells) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &polygon, 9, CENTER_CONTAINMENT, &numHexagons)); + &polygon, 9, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &polygon, 9, CENTER_CONTAINMENT, hexagons)); + &polygon, 9, CONTAINMENT_CENTER, hexagons)); int found = 0; int numPentagons = 0; @@ -497,7 +497,7 @@ SUITE(polygonToCells) { "maxPolygonToCellsSize"); } t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &sfGeoPolygon, 9, CENTER_CONTAINMENT, &numHexagons)); + &sfGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); for (uint32_t flags = 3; flags <= 32; flags++) { t_assert(H3_EXPORT(polygonToCellsExperimental)( diff --git a/src/apps/testapps/testPolygonToCellsReportedExperimental.c b/src/apps/testapps/testPolygonToCellsReportedExperimental.c index 54aee4136..b3fe8e5b8 100644 --- a/src/apps/testapps/testPolygonToCellsReportedExperimental.c +++ b/src/apps/testapps/testPolygonToCellsReportedExperimental.c @@ -43,25 +43,25 @@ SUITE(polygonToCells_reported) { for (int res = 0; res < 3; res++) { int64_t polygonToCellsSize; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &worldGeoPolygon, res, CENTER_CONTAINMENT, + &worldGeoPolygon, res, CONTAINMENT_CENTER, &polygonToCellsSize)); H3Index *polygonToCellsOut = calloc(polygonToCellsSize, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &worldGeoPolygon, res, CENTER_CONTAINMENT, polygonToCellsOut)); + &worldGeoPolygon, res, CONTAINMENT_CENTER, polygonToCellsOut)); int64_t actualNumIndexes = countNonNullIndexes(polygonToCellsOut, polygonToCellsSize); int64_t polygonToCellsSize2; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &worldGeoPolygon2, res, CENTER_CONTAINMENT, + &worldGeoPolygon2, res, CONTAINMENT_CENTER, &polygonToCellsSize2)); H3Index *polygonToCellsOut2 = calloc(polygonToCellsSize2, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &worldGeoPolygon2, res, CENTER_CONTAINMENT, + &worldGeoPolygon2, res, CONTAINMENT_CENTER, polygonToCellsOut2)); int64_t actualNumIndexes2 = countNonNullIndexes(polygonToCellsOut2, polygonToCellsSize2); @@ -110,11 +110,11 @@ SUITE(polygonToCells_reported) { int res = 7; int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &testPolygon, res, CENTER_CONTAINMENT, &numHexagons)); + &testPolygon, res, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &testPolygon, res, CENTER_CONTAINMENT, hexagons)); + &testPolygon, res, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 4499, @@ -139,11 +139,11 @@ SUITE(polygonToCells_reported) { int res = 7; int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &testPolygon, res, CENTER_CONTAINMENT, &numHexagons)); + &testPolygon, res, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &testPolygon, res, CENTER_CONTAINMENT, hexagons)); + &testPolygon, res, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 4609, @@ -165,11 +165,11 @@ SUITE(polygonToCells_reported) { int res = 13; int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &testPolygon, res, CENTER_CONTAINMENT, &numHexagons)); + &testPolygon, res, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &testPolygon, res, CENTER_CONTAINMENT, hexagons)); + &testPolygon, res, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 4353, "got expected polygonToCells size"); @@ -198,11 +198,11 @@ SUITE(polygonToCells_reported) { int res = 5; int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( - &testPolygon, res, CENTER_CONTAINMENT, &numHexagons)); + &testPolygon, res, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &testPolygon, res, CENTER_CONTAINMENT, hexagons)); + &testPolygon, res, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 8, "got expected polygonToCells size"); diff --git a/src/h3lib/include/polygon.h b/src/h3lib/include/polygon.h index 3a07e6f7e..d7d24ffbc 100644 --- a/src/h3lib/include/polygon.h +++ b/src/h3lib/include/polygon.h @@ -45,10 +45,10 @@ * the `flags` bit field. */ typedef enum { - CENTER_CONTAINMENT = 0, - FULL_CONTAINMENT = 1, - OVERLAPPING = 2, - INVALID_CONTAINMENT = 3 + CONTAINMENT_CENTER = 0, ///< Cell center is contained in the shape + CONTAINMENT_FULL = 1, ///< Cell is fully contained in the shape + CONTAINMENT_OVERLAPPING = 2, ///< Cell overlaps the shape at any point + CONTAINMENT_INVALID = 3 ///< This mode is invalid and should not be used } ContainmentMode; // 1s in the 4 bits defining the polyfill containment mode, 0s elsewhere diff --git a/src/h3lib/lib/polyfill.c b/src/h3lib/lib/polyfill.c index 8a957f9f6..4e23c2b94 100644 --- a/src/h3lib/lib/polyfill.c +++ b/src/h3lib/lib/polyfill.c @@ -414,7 +414,7 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { // Target res: Do a fine-grained check if (cellRes == iter->_res) { - if (mode == CENTER_CONTAINMENT || mode == OVERLAPPING) { + if (mode == CONTAINMENT_CENTER || mode == CONTAINMENT_OVERLAPPING) { // Check if the cell center is inside the polygon LatLng center; H3Error centerErr = H3_EXPORT(cellToLatLng)(cell, ¢er); @@ -429,7 +429,7 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { return; } } - if (mode == FULL_CONTAINMENT || mode == OVERLAPPING) { + if (mode == CONTAINMENT_FULL || mode == CONTAINMENT_OVERLAPPING) { CellBoundary boundary; H3Error boundaryErr = H3_EXPORT(cellToBoundary)(cell, &boundary); @@ -444,7 +444,7 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { return; } // Check if the cell is fully contained by the polygon - if (mode == FULL_CONTAINMENT && + if (mode == CONTAINMENT_FULL && cellBoundaryInsidePolygon(iter->_polygon, iter->_bboxes, &boundary, &bbox)) { // Set to next output @@ -454,7 +454,7 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { // For overlap, we've already checked for center point inclusion // above; if that failed, we only need to check for line // intersection - else if (mode == OVERLAPPING && + else if (mode == CONTAINMENT_OVERLAPPING && cellBoundaryCrossesPolygon( iter->_polygon, iter->_bboxes, &boundary, &bbox)) { // Set to next output diff --git a/src/h3lib/lib/polygon.c b/src/h3lib/lib/polygon.c index 3685d0b7a..1944e5bda 100644 --- a/src/h3lib/lib/polygon.c +++ b/src/h3lib/lib/polygon.c @@ -50,7 +50,7 @@ */ H3Error validatePolygonFlags(uint32_t flags) { if (flags & (~FLAG_CONTAINMENT_MODE_MASK) || - FLAG_GET_CONTAINMENT_MODE(flags) >= INVALID_CONTAINMENT) { + FLAG_GET_CONTAINMENT_MODE(flags) >= CONTAINMENT_INVALID) { return E_OPTION_INVALID; } return E_SUCCESS; From 1a446270955dda27cccaa261980578f40c7cf9bf Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Tue, 7 Nov 2023 17:48:01 -0800 Subject: [PATCH 05/25] Fix and test for case where OVERLAPPING cell contains polygon --- .../benchmarkPolygonToCellsExperimental.c | 60 ++++++++++++++- .../testapps/testPolygonToCellsExperimental.c | 75 ++++++++++++++++++- src/h3lib/lib/polyfill.c | 18 +++++ 3 files changed, 148 insertions(+), 5 deletions(-) diff --git a/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c b/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c index b475cd783..1034f3372 100644 --- a/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c +++ b/src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c @@ -123,7 +123,7 @@ southernGeoPolygon.geoloop = southernGeoLoop; int64_t numHexagons; H3Index *hexagons; -BENCHMARK(polygonToCellsSF, 500, { +BENCHMARK(polygonToCellsSF_Center, 500, { H3_EXPORT(maxPolygonToCellsSize) (&sfGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); @@ -132,7 +132,25 @@ BENCHMARK(polygonToCellsSF, 500, { free(hexagons); }); -BENCHMARK(polygonToCellsAlameda, 500, { +BENCHMARK(polygonToCellsSF_Full, 500, { + H3_EXPORT(maxPolygonToCellsSize) + (&sfGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons); + hexagons = calloc(numHexagons, sizeof(H3Index)); + H3_EXPORT(polygonToCellsExperimental) + (&sfGeoPolygon, 9, CONTAINMENT_FULL, hexagons); + free(hexagons); +}); + +BENCHMARK(polygonToCellsSF_Overlapping, 500, { + H3_EXPORT(maxPolygonToCellsSize) + (&sfGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons); + hexagons = calloc(numHexagons, sizeof(H3Index)); + H3_EXPORT(polygonToCellsExperimental) + (&sfGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons); + free(hexagons); +}); + +BENCHMARK(polygonToCellsAlameda_Center, 500, { H3_EXPORT(maxPolygonToCellsSize) (&alamedaGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); @@ -141,7 +159,25 @@ BENCHMARK(polygonToCellsAlameda, 500, { free(hexagons); }); -BENCHMARK(polygonToCellsSouthernExpansion, 10, { +BENCHMARK(polygonToCellsAlameda_Full, 500, { + H3_EXPORT(maxPolygonToCellsSize) + (&alamedaGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons); + hexagons = calloc(numHexagons, sizeof(H3Index)); + H3_EXPORT(polygonToCellsExperimental) + (&alamedaGeoPolygon, 9, CONTAINMENT_FULL, hexagons); + free(hexagons); +}); + +BENCHMARK(polygonToCellsAlameda_Overlapping, 500, { + H3_EXPORT(maxPolygonToCellsSize) + (&alamedaGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons); + hexagons = calloc(numHexagons, sizeof(H3Index)); + H3_EXPORT(polygonToCellsExperimental) + (&alamedaGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons); + free(hexagons); +}); + +BENCHMARK(polygonToCellsSouthernExpansion_Center, 10, { H3_EXPORT(maxPolygonToCellsSize) (&southernGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons); hexagons = calloc(numHexagons, sizeof(H3Index)); @@ -150,4 +186,22 @@ BENCHMARK(polygonToCellsSouthernExpansion, 10, { free(hexagons); }); +BENCHMARK(polygonToCellsSouthernExpansion_Full, 10, { + H3_EXPORT(maxPolygonToCellsSize) + (&southernGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons); + hexagons = calloc(numHexagons, sizeof(H3Index)); + H3_EXPORT(polygonToCellsExperimental) + (&southernGeoPolygon, 9, CONTAINMENT_FULL, hexagons); + free(hexagons); +}); + +BENCHMARK(polygonToCellsSouthernExpansion_Overlapping, 10, { + H3_EXPORT(maxPolygonToCellsSize) + (&southernGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons); + hexagons = calloc(numHexagons, sizeof(H3Index)); + H3_EXPORT(polygonToCellsExperimental) + (&southernGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons); + free(hexagons); +}); + END_BENCHMARKS(); diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index cf359a4ce..239a1fb88 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -173,7 +173,7 @@ SUITE(polygonToCells) { free(hexagons); } - TEST(polygonToCellsFullContainment) { + TEST(polygonToCells_FullContainment) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( &sfGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); @@ -188,7 +188,7 @@ SUITE(polygonToCells) { free(hexagons); } - TEST(polygonToCellsOverlapping) { + TEST(polygonToCells_Overlapping) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( &sfGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); @@ -264,6 +264,77 @@ SUITE(polygonToCells) { free(hexagons); } + TEST(polygonToCellsContainsPolygon) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &sfGeoPolygon, 4, CONTAINMENT_CENTER, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &sfGeoPolygon, 4, CONTAINMENT_CENTER, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, "got expected polygonToCells size"); + free(hexagons); + } + + TEST(polygonToCellsContainsPolygon_CenterContained) { + // Contains the center point of a res 4 cell + static LatLng centerVerts[] = {{0.6595645, -2.1353315}, + {0.6595645, -2.1353314}, + {0.6595644, -2.1353314}, + {0.6595644, -2.1353314265}}; + static GeoLoop centerGeoLoop = {.numVerts = 6, .verts = centerVerts}; + static GeoPolygon centerGeoPolygon; + centerGeoPolygon.geoloop = centerGeoLoop; + centerGeoPolygon.numHoles = 0; + + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + ¢erGeoPolygon, 4, CONTAINMENT_CENTER, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + ¢erGeoPolygon, 4, CONTAINMENT_CENTER, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 1, "got expected polygonToCells size"); + t_assert(hexagons[0] == 0x8428309ffffffff, "got expected hexagon"); + + free(hexagons); + } + + TEST(polygonToCellsContainsPolygon_FullContainment) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &sfGeoPolygon, 4, CONTAINMENT_FULL, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &sfGeoPolygon, 4, CONTAINMENT_FULL, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, + "got expected polygonToCells size (full containment mode)"); + free(hexagons); + } + + TEST(polygonToCellsContainsPolygon_Overlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)( + &sfGeoPolygon, 4, CONTAINMENT_OVERLAPPING, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &sfGeoPolygon, 4, CONTAINMENT_OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 1, + "got expected polygonToCells size (overlapping mode)"); + t_assert(hexagons[0] == 0x8428309ffffffff, "got expected hexagon"); + free(hexagons); + } + TEST(polygonToCellsExact) { LatLng somewhere = {1, 2}; H3Index origin; diff --git a/src/h3lib/lib/polyfill.c b/src/h3lib/lib/polyfill.c index 4e23c2b94..f62d56806 100644 --- a/src/h3lib/lib/polyfill.c +++ b/src/h3lib/lib/polyfill.c @@ -429,6 +429,24 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { return; } } + if (mode == CONTAINMENT_OVERLAPPING) { + // For overlapping, we need to do a quick check to determine + // whether the polygon is wholly contained by the cell. We check + // the first polygon vertex, which if it is contained could also + // mean we simply intersect. + H3Index polygonCell; + H3Error polygonCellErr = H3_EXPORT(latLngToCell)( + &(iter->_polygon->geoloop.verts[0]), cellRes, &polygonCell); + if (polygonCellErr != E_SUCCESS) { + iterErrorPolygonCompact(iter, polygonCellErr); + return; + } + if (polygonCell == cell) { + // Set to next output + iter->cell = cell; + return; + } + } if (mode == CONTAINMENT_FULL || mode == CONTAINMENT_OVERLAPPING) { CellBoundary boundary; H3Error boundaryErr = From edaa901b55fc872a660109f37014f44398ea6e0f Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Tue, 7 Nov 2023 18:39:33 -0800 Subject: [PATCH 06/25] Possibly fix test --- src/apps/testapps/testPolygonToCellsExperimental.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 239a1fb88..06f240824 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -280,12 +280,12 @@ SUITE(polygonToCells) { TEST(polygonToCellsContainsPolygon_CenterContained) { // Contains the center point of a res 4 cell - static LatLng centerVerts[] = {{0.6595645, -2.1353315}, - {0.6595645, -2.1353314}, - {0.6595644, -2.1353314}, - {0.6595644, -2.1353314265}}; - static GeoLoop centerGeoLoop = {.numVerts = 6, .verts = centerVerts}; - static GeoPolygon centerGeoPolygon; + LatLng centerVerts[] = {{0.6595645, -2.1353315}, + {0.6595645, -2.1353314}, + {0.6595644, -2.1353314}, + {0.6595644, -2.1353314265}}; + GeoLoop centerGeoLoop = {.numVerts = 6, .verts = centerVerts}; + GeoPolygon centerGeoPolygon; centerGeoPolygon.geoloop = centerGeoLoop; centerGeoPolygon.numHoles = 0; From fedaa5f897a40e4b53a5e42c7e1d203b25ce024f Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Tue, 7 Nov 2023 22:46:42 -0800 Subject: [PATCH 07/25] Fixture fix --- src/apps/testapps/testPolygonToCellsExperimental.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 06f240824..24464597d 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -284,7 +284,7 @@ SUITE(polygonToCells) { {0.6595645, -2.1353314}, {0.6595644, -2.1353314}, {0.6595644, -2.1353314265}}; - GeoLoop centerGeoLoop = {.numVerts = 6, .verts = centerVerts}; + GeoLoop centerGeoLoop = {.numVerts = 4, .verts = centerVerts}; GeoPolygon centerGeoPolygon; centerGeoPolygon.geoloop = centerGeoLoop; centerGeoPolygon.numHoles = 0; From b3eb114480c108ce20b816d1fe850e3e0f302872 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Wed, 15 Nov 2023 16:56:20 -0800 Subject: [PATCH 08/25] add polygonToCellsExperimental fuzzers --- CMakeLists.txt | 4 + src/apps/fuzzers/README.md | 7 +- src/apps/fuzzers/fuzzerPolygonToCells.c | 18 +-- .../fuzzerPolygonToCellsExperimental.c | 105 ++++++++++++++++++ .../fuzzerPolygonToCellsExperimentalNoHoles.c | 63 +++++++++++ .../fuzzers/fuzzerPolygonToCellsNoHoles.c | 6 +- 6 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c create mode 100644 src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c90917a1..87fbbca36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,7 +272,9 @@ set(OTHER_SOURCE_FILES src/apps/fuzzers/fuzzerDirectedEdge.c src/apps/fuzzers/fuzzerLocalIj.c src/apps/fuzzers/fuzzerPolygonToCells.c + src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c + src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c src/apps/fuzzers/fuzzerCellToChildPos.c src/apps/fuzzers/fuzzerInternalAlgos.c src/apps/fuzzers/fuzzerInternalCoordIjk.c @@ -547,7 +549,9 @@ if(BUILD_FUZZERS) add_h3_fuzzer(fuzzerDirectedEdge src/apps/fuzzers/fuzzerDirectedEdge.c) add_h3_fuzzer(fuzzerLocalIj src/apps/fuzzers/fuzzerLocalIj.c) add_h3_fuzzer(fuzzerPolygonToCells src/apps/fuzzers/fuzzerPolygonToCells.c) + add_h3_fuzzer(fuzzerPolygonToCellsExperimental src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c) add_h3_fuzzer(fuzzerPolygonToCellsNoHoles src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c) + add_h3_fuzzer(fuzzerPolygonToCellsExperimentalNoHoles src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c) add_h3_fuzzer(fuzzerCellToChildPos src/apps/fuzzers/fuzzerCellToChildPos.c) if(ENABLE_REQUIRES_ALL_SYMBOLS) add_h3_fuzzer(fuzzerInternalAlgos src/apps/fuzzers/fuzzerInternalAlgos.c) diff --git a/src/apps/fuzzers/README.md b/src/apps/fuzzers/README.md index 392c9c206..a0e386e71 100644 --- a/src/apps/fuzzers/README.md +++ b/src/apps/fuzzers/README.md @@ -11,8 +11,8 @@ such as the H3 core library. The public API of H3 is covered in the following fuzzers: -| Function | File or status -| -------- | -------------- +| Function | File +| -------- | ---- | latLngToCell | [fuzzerLatLngToCell](./fuzzerLatLngToCell.c) | cellToLatLng | [fuzzerCellToLatLng](./fuzzerCellToLatLng.c) | cellToBoundary | [fuzzerCellToLatLng](./fuzzerCellToLatLng.c) @@ -20,7 +20,8 @@ The public API of H3 is covered in the following fuzzers: | gridDiskDistances | [fuzzerGridDisk](./fuzzerGridDisk.c) | gridRingUnsafe | [fuzzerGridDisk](./fuzzerGridDisk.c) | gridDisksUnsafe | [fuzzerGridDisk](./fuzzerGridDisk.c) -| polygonToCells | [fuzzerPoylgonToCells](./fuzzerPolygonToCells.c) +| polygonToCells | [fuzzerPoylgonToCells](./fuzzerPolygonToCells.c) [fuzzerPoylgonToCellsNoHoles](./fuzzerPolygonToCellsNoHoles.c) +| polygonToCellsExperimental | [fuzzerPoylgonToCellsExperimental](./fuzzerPolygonToCellsExperimental.c) [fuzzerPoylgonToCellsExperimentalNoHoles](./fuzzerPolygonToCellsExperimentalNoHoles.c) | h3SetToMultiPolygon | [fuzzerH3SetToLinkedGeo](./fuzzerH3SetToLinkedGeo.c) | degsToRads | Trivial | radsToDegs | Trivial diff --git a/src/apps/fuzzers/fuzzerPolygonToCells.c b/src/apps/fuzzers/fuzzerPolygonToCells.c index 2c5333f9a..84463037b 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCells.c +++ b/src/apps/fuzzers/fuzzerPolygonToCells.c @@ -1,5 +1,5 @@ /* - * Copyright 2022 Uber Technologies, Inc. + * Copyright 2023 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. @@ -14,11 +14,12 @@ * limitations under the License. */ /** @file - * @brief Fuzzer program for polygonToCells and related functions + * @brief Fuzzer program for polygonToCellsExperimental and related functions */ #include "aflHarness.h" #include "h3api.h" +#include "polygon.h" #include "utility.h" typedef struct { @@ -71,7 +72,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { int res = args->res % (MAX_RES + 1); GeoPolygon geoPolygon; - geoPolygon.numHoles = args->numHoles % MAX_HOLES; + int originalNumHoles = args->numHoles % MAX_HOLES; + geoPolygon.numHoles = originalNumHoles; if (geoPolygon.numHoles < 0) { return 0; } @@ -88,10 +90,12 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { } } - // TODO: Fuzz the `flags` input as well when it has meaningful input - run(&geoPolygon, 0, res); - geoPolygon.numHoles = 0; - run(&geoPolygon, 0, res); + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + geoPolygon.numHoles = originalNumHoles; + run(&geoPolygon, 0, res); + geoPolygon.numHoles = 0; + run(&geoPolygon, 0, res); + } free(geoPolygon.holes); return 0; diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c b/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c new file mode 100644 index 000000000..109f34baa --- /dev/null +++ b/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c @@ -0,0 +1,105 @@ +/* + * Copyright 2023 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 Fuzzer program for polygonToCells2 and related functions + */ + +#include "aflHarness.h" +#include "h3api.h" +#include "polyfill.h" +#include "polygon.h" +#include "utility.h" + +typedef struct { + int res; + int numHoles; + // repeating: num verts, verts + // We add a large fixed buffer so our test case generator for AFL + // knows how large to make the file. + uint8_t buffer[1024]; +} inputArgs; + +const int MAX_RES = 15; +const int MAX_SZ = 4000000; +const int MAX_HOLES = 100; + +int populateGeoLoop(GeoLoop *g, const uint8_t *data, size_t *offset, + size_t size) { + if (size < *offset + sizeof(int)) { + return 1; + } + int numVerts = *(const int *)(data + *offset); + *offset = *offset + sizeof(int); + g->numVerts = numVerts; + if (size < *offset + sizeof(LatLng) * numVerts) { + return 1; + } + g->verts = (LatLng *)(data + *offset); + *offset = *offset + sizeof(LatLng) * numVerts; + return 0; +} + +void run(GeoPolygon *geoPolygon, uint32_t flags, int res) { + int64_t sz; + H3Error err = H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &sz); + if (!err && sz < MAX_SZ) { + H3Index *out = calloc(sz, sizeof(H3Index)); + H3_EXPORT(polygonToCellsExperimental)(geoPolygon, res, flags, out); + free(out); + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + // TODO: It is difficult for the fuzzer to generate inputs that are + // considered valid by this fuzzer. fuzzerPolygonToCellsNoHoles.c + // is a workaround for that. + if (size < sizeof(inputArgs)) { + return 0; + } + const inputArgs *args = (const inputArgs *)data; + int res = args->res % (MAX_RES + 1); + + GeoPolygon geoPolygon; + int originalNumHoles = args->numHoles % MAX_HOLES; + geoPolygon.numHoles = originalNumHoles; + if (geoPolygon.numHoles < 0) { + return 0; + } + geoPolygon.holes = calloc(geoPolygon.numHoles, sizeof(GeoLoop)); + size_t offset = sizeof(inputArgs) - sizeof(args->buffer); + if (populateGeoLoop(&geoPolygon.geoloop, data, &offset, size)) { + free(geoPolygon.holes); + return 0; + } + for (int i = 0; i < geoPolygon.numHoles; i++) { + if (populateGeoLoop(&geoPolygon.holes[i], data, &offset, size)) { + free(geoPolygon.holes); + return 0; + } + } + + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + geoPolygon.numHoles = originalNumHoles; + run(&geoPolygon, flags, res); + geoPolygon.numHoles = 0; + run(&geoPolygon, flags, res); + } + free(geoPolygon.holes); + + return 0; +} + +AFL_HARNESS_MAIN(sizeof(inputArgs)); diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c b/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c new file mode 100644 index 000000000..0649887ad --- /dev/null +++ b/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c @@ -0,0 +1,63 @@ +/* + * Copyright 2023 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 Fuzzer program for polygonToCellsExperimental and related functions, + * without holes + */ + +#include "aflHarness.h" +#include "h3api.h" +#include "polyfill.h" +#include "polygon.h" +#include "utility.h" + +const int MAX_RES = 15; +const int MAX_SZ = 4000000; + +void run(GeoPolygon *geoPolygon, uint32_t flags, int res) { + int64_t sz; + H3Error err = H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &sz); + if (!err && sz < MAX_SZ) { + H3Index *out = calloc(sz, sizeof(H3Index)); + H3_EXPORT(polygonToCellsExperimental)(geoPolygon, res, flags, out); + free(out); + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < sizeof(int)) { + return 0; + } + + uint8_t res = *data; + size_t vertsSize = size - 1; + int numVerts = vertsSize / sizeof(LatLng); + + GeoPolygon geoPolygon; + geoPolygon.numHoles = 0; + geoPolygon.holes = NULL; + geoPolygon.geoloop.numVerts = numVerts; + // Offset by 1 since *data was used for `res`, above. + geoPolygon.geoloop.verts = (LatLng *)(data + 1); + + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + run(&geoPolygon, flags, res); + } + + return 0; +} + +AFL_HARNESS_MAIN(sizeof(H3Index) * 1024); diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c b/src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c index 72558b51e..546c6eb46 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c +++ b/src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c @@ -19,6 +19,7 @@ #include "aflHarness.h" #include "h3api.h" +#include "polygon.h" #include "utility.h" const int MAX_RES = 15; @@ -50,8 +51,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // Offset by 1 since *data was used for `res`, above. geoPolygon.geoloop.verts = (LatLng *)(data + 1); - // TODO: Fuzz the `flags` input as well when it has meaningful input - run(&geoPolygon, 0, res); + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + run(&geoPolygon, 0, res); + } return 0; } From a2adc0467f71465c2a0db989f2f82ac9ae7e3066 Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Sat, 25 Nov 2023 08:49:05 -0800 Subject: [PATCH 09/25] Additional test coverage for error cases --- src/apps/testapps/testPolyfillInternal.c | 62 +++++++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/src/apps/testapps/testPolyfillInternal.c b/src/apps/testapps/testPolyfillInternal.c index db393cc30..75a2b55f4 100644 --- a/src/apps/testapps/testPolyfillInternal.c +++ b/src/apps/testapps/testPolyfillInternal.c @@ -14,11 +14,14 @@ * limitations under the License. */ +#include + #include "bbox.h" #include "h3Index.h" #include "h3api.h" #include "latLng.h" #include "polyfill.h" +#include "polygon.h" #include "test.h" #include "utility.h" @@ -33,16 +36,24 @@ static GeoPolygon sfGeoPolygon = { {0.6599990002976, -2.1376771158464}}}, .numHoles = 0}; +static GeoPolygon invalidGeoPolygon = { + .geoloop = {.numVerts = 4, + .verts = (LatLng[]){{NAN, -2.1364398519396}, + {0.6595011102219, NAN}, + {NAN, -2.1354884206045}, + {0.6581220034068, NAN}}}, + .numHoles = 0}; + SUITE(polyfillInternal) { TEST(iterInitPolygonCompact_errors) { IterCellsPolygonCompact iter; - iter = iterInitPolygonCompact(&sfGeoPolygon, -1, 0); + iter = iterInitPolygonCompact(&sfGeoPolygon, -1, CONTAINMENT_CENTER); t_assert(iter.error == E_RES_DOMAIN, "Got expected error for invalid res"); t_assert(iter.cell == H3_NULL, "Got null output for invalid res"); - iter = iterInitPolygonCompact(&sfGeoPolygon, 16, 0); + iter = iterInitPolygonCompact(&sfGeoPolygon, 16, CONTAINMENT_CENTER); t_assert(iter.error == E_RES_DOMAIN, "Got expected error for invalid res"); t_assert(iter.cell == H3_NULL, "Got null output for invalid res"); @@ -53,11 +64,12 @@ SUITE(polyfillInternal) { t_assert(iter.cell == H3_NULL, "Got null output for invalid flags"); } - TEST(iterStepPolygonCompact_errors) { + TEST(iterStepPolygonCompact_invalidCellErrors) { IterCellsPolygonCompact iter; H3Index cell; - iter = iterInitPolygonCompact(&sfGeoPolygon, 9, 0); + iter = iterInitPolygonCompact(&sfGeoPolygon, 9, CONTAINMENT_CENTER); + t_assertSuccess(iter.error); // Give the iterator a cell with a bad base cell cell = 0x85283473fffffff; @@ -69,7 +81,22 @@ SUITE(polyfillInternal) { "Got expected error for invalid cell"); t_assert(iter.cell == H3_NULL, "Got null output for invalid cell"); - iter = iterInitPolygonCompact(&sfGeoPolygon, 9, 0); + iter = iterInitPolygonCompact(&sfGeoPolygon, 9, CONTAINMENT_CENTER); + t_assertSuccess(iter.error); + + // Give the iterator a cell with a bad base cell, at the target res + cell = 0x89283470003ffff; + H3_SET_BASE_CELL(cell, 123); + iter.cell = cell; + + iterStepPolygonCompact(&iter); + t_assert(iter.error == E_CELL_INVALID, + "Got expected error for invalid cell"); + t_assert(iter.cell == H3_NULL, + "Got null output for invalid cell at res"); + + iter = iterInitPolygonCompact(&sfGeoPolygon, 9, CONTAINMENT_CENTER); + t_assertSuccess(iter.error); // Give the iterator a cell that's too fine for a child check, // and a target resolution that allows this to run. This cell has @@ -84,9 +111,28 @@ SUITE(polyfillInternal) { t_assert(iter.cell == H3_NULL, "Got null output for invalid cell"); } + TEST(iterStepPolygonCompact_invalidPolygonErrors) { + IterCellsPolygonCompact iter; + + // Start with a good polygon, otherwise we error out early + iter = + iterInitPolygonCompact(&sfGeoPolygon, 5, CONTAINMENT_OVERLAPPING); + t_assertSuccess(iter.error); + + // Give the iterator a bad polygon and a cell at target res + iter._polygon = &invalidGeoPolygon; + iter.cell = 0x85283473fffffff; + + iterStepPolygonCompact(&iter); + t_assert(iter.error == E_LATLNG_DOMAIN, + "Got expected error for invalid polygon"); + t_assert(iter.cell == H3_NULL, "Got null output for invalid cell"); + } + TEST(iterDestroyPolygonCompact) { IterCellsPolygonCompact iter = - iterInitPolygonCompact(&sfGeoPolygon, 9, 0); + iterInitPolygonCompact(&sfGeoPolygon, 9, CONTAINMENT_CENTER); + t_assertSuccess(iter.error); iterDestroyPolygonCompact(&iter); t_assert(iter.error == E_SUCCESS, "Got success for destroyed iterator"); @@ -101,7 +147,9 @@ SUITE(polyfillInternal) { } TEST(iterDestroyPolygon) { - IterCellsPolygon iter = iterInitPolygon(&sfGeoPolygon, 9, 0); + IterCellsPolygon iter = + iterInitPolygon(&sfGeoPolygon, 9, CONTAINMENT_CENTER); + t_assertSuccess(iter.error); iterDestroyPolygon(&iter); t_assert(iter.error == E_SUCCESS, "Got success for destroyed iterator"); From f0f1b45adff09a01f09c9fc29fc6e9e0f7c1cf56 Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Sat, 25 Nov 2023 14:45:02 -0800 Subject: [PATCH 10/25] Test coverage for one more error, excluding unreachable block from coverage --- src/apps/testapps/testPolyfillInternal.c | 15 +++++++++++++++ src/h3lib/lib/polyfill.c | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/apps/testapps/testPolyfillInternal.c b/src/apps/testapps/testPolyfillInternal.c index 75a2b55f4..4e44255ad 100644 --- a/src/apps/testapps/testPolyfillInternal.c +++ b/src/apps/testapps/testPolyfillInternal.c @@ -89,6 +89,21 @@ SUITE(polyfillInternal) { H3_SET_BASE_CELL(cell, 123); iter.cell = cell; + iterStepPolygonCompact(&iter); + t_assert(iter.error == E_CELL_INVALID, + "Got expected error for invalid cell"); + t_assert(iter.cell == H3_NULL, + "Got null output for invalid cell at res"); + + iter = iterInitPolygonCompact(&sfGeoPolygon, 9, CONTAINMENT_FULL); + t_assertSuccess(iter.error); + + // Give the iterator a cell with a bad base cell, at the target res + // (full containment) + cell = 0x89283470003ffff; + H3_SET_BASE_CELL(cell, 123); + iter.cell = cell; + iterStepPolygonCompact(&iter); t_assert(iter.error == E_CELL_INVALID, "Got expected error for invalid cell"); diff --git a/src/h3lib/lib/polyfill.c b/src/h3lib/lib/polyfill.c index f62d56806..bde5eedbc 100644 --- a/src/h3lib/lib/polyfill.c +++ b/src/h3lib/lib/polyfill.c @@ -457,7 +457,9 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { } BBox bbox; H3Error bboxErr = cellToBBox(cell, &bbox, false); - if (bboxErr) { + if (NEVER(bboxErr != E_SUCCESS)) { + // Should be unreachable - invalid cells would be caught in + // the previous boundaryErr iterErrorPolygonCompact(iter, bboxErr); return; } From 4226e437f00e90d9e55e6d3a51b27a28717945a0 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Sun, 26 Nov 2023 08:27:03 -0800 Subject: [PATCH 11/25] fix fuzzer comment --- src/apps/fuzzers/fuzzerPolygonToCells.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/fuzzers/fuzzerPolygonToCells.c b/src/apps/fuzzers/fuzzerPolygonToCells.c index 84463037b..cc6b77da0 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCells.c +++ b/src/apps/fuzzers/fuzzerPolygonToCells.c @@ -1,5 +1,5 @@ /* - * Copyright 2023 Uber Technologies, Inc. + * Copyright 2022-2023 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. @@ -14,7 +14,7 @@ * limitations under the License. */ /** @file - * @brief Fuzzer program for polygonToCellsExperimental and related functions + * @brief Fuzzer program for polygonToCells and related functions */ #include "aflHarness.h" From b055cd5af149ce164dcfe36eb5a99cf4927eb1d5 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Mon, 4 Dec 2023 12:01:10 -0800 Subject: [PATCH 12/25] fix size estimation --- src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c | 3 ++- src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c b/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c index 109f34baa..23a9b43fa 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c +++ b/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c @@ -54,7 +54,8 @@ int populateGeoLoop(GeoLoop *g, const uint8_t *data, size_t *offset, void run(GeoPolygon *geoPolygon, uint32_t flags, int res) { int64_t sz; - H3Error err = H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &sz); + H3Error err = H3_EXPORT(maxPolygonToCellsSizeExperimental)(geoPolygon, res, + flags, &sz); if (!err && sz < MAX_SZ) { H3Index *out = calloc(sz, sizeof(H3Index)); H3_EXPORT(polygonToCellsExperimental)(geoPolygon, res, flags, out); diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c b/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c index 0649887ad..eca247d5e 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c +++ b/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c @@ -29,7 +29,8 @@ const int MAX_SZ = 4000000; void run(GeoPolygon *geoPolygon, uint32_t flags, int res) { int64_t sz; - H3Error err = H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &sz); + H3Error err = H3_EXPORT(maxPolygonToCellsSizeExperimental)(geoPolygon, res, + flags, &sz); if (!err && sz < MAX_SZ) { H3Index *out = calloc(sz, sizeof(H3Index)); H3_EXPORT(polygonToCellsExperimental)(geoPolygon, res, flags, out); From 629c65481c7b0cd464586540a51b003d1519d6e0 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Thu, 14 Dec 2023 16:50:20 -0800 Subject: [PATCH 13/25] add test --- .../testapps/testPolygonToCellsExperimental.c | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 4d48c3c1d..08ccf745f 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -300,7 +300,52 @@ SUITE(polygonToCells) { int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (empty)"); + "got expected polygonToCells size (empty center)"); + free(hexagons); + } + + TEST(polygonToCellsEmptyContainsOverlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &emptyGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &emptyGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, + "got expected polygonToCells size (empty overlapping)"); + free(hexagons); + } + + TEST(polygonToCellsEmpty) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &emptyGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &emptyGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, + "got expected polygonToCells size (empty full)"); + free(hexagons); + } + + TEST(polygonToCellsEmptyOverlappingBbox) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &emptyGeoPolygon, 9, CONTAINMENT_OVERLAPPING_BBOX, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &emptyGeoPolygon, 9, CONTAINMENT_OVERLAPPING_BBOX, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, + "got expected polygonToCells size (empty overlapping bbox)"); free(hexagons); } From 641d481b94d0e1980767402211d38af242cb881d Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Thu, 14 Dec 2023 16:57:06 -0800 Subject: [PATCH 14/25] add null test --- .../testapps/testPolygonToCellsExperimental.c | 41 ++++++++++++++----- src/h3lib/lib/polyfill.c | 7 ++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 08ccf745f..a754119cf 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -46,6 +46,9 @@ static LatLng emptyVerts[] = {{0.659966917655, -2.1364398519394}, static GeoLoop emptyGeoLoop = {.numVerts = 3, .verts = emptyVerts}; static GeoPolygon emptyGeoPolygon; +static GeoLoop nullGeoLoop = {.numVerts = 0}; +static GeoPolygon nullGeoPolygon; + static LatLng invalidVerts[] = {{INFINITY, INFINITY}, {-INFINITY, -INFINITY}}; static GeoLoop invalidGeoLoop = {.numVerts = 2, .verts = invalidVerts}; static GeoPolygon invalidGeoPolygon; @@ -147,6 +150,9 @@ SUITE(polygonToCells) { emptyGeoPolygon.geoloop = emptyGeoLoop; emptyGeoPolygon.numHoles = 0; + nullGeoPolygon.geoloop = nullGeoLoop; + nullGeoPolygon.numHoles = 0; + invalidGeoPolygon.geoloop = invalidGeoLoop; invalidGeoPolygon.numHoles = 0; @@ -304,48 +310,63 @@ SUITE(polygonToCells) { free(hexagons); } + TEST(polygonToCellsNull) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &nullGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &nullGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, + "got expected polygonToCells size (null center)"); + free(hexagons); + } + TEST(polygonToCellsEmptyContainsOverlapping) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &emptyGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); + &nullGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &emptyGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); + &nullGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (empty overlapping)"); + "got expected polygonToCells size (null overlapping)"); free(hexagons); } - TEST(polygonToCellsEmpty) { + TEST(polygonToCellsEmptyFull) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &emptyGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); + &nullGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &emptyGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); + &nullGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (empty full)"); + "got expected polygonToCells size (null full)"); free(hexagons); } TEST(polygonToCellsEmptyOverlappingBbox) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &emptyGeoPolygon, 9, CONTAINMENT_OVERLAPPING_BBOX, &numHexagons)); + &nullGeoPolygon, 9, CONTAINMENT_OVERLAPPING_BBOX, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &emptyGeoPolygon, 9, CONTAINMENT_OVERLAPPING_BBOX, hexagons)); + &nullGeoPolygon, 9, CONTAINMENT_OVERLAPPING_BBOX, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (empty overlapping bbox)"); + "got expected polygonToCells size (null overlapping bbox)"); free(hexagons); } diff --git a/src/h3lib/lib/polyfill.c b/src/h3lib/lib/polyfill.c index 688cb1529..46c9c85a9 100644 --- a/src/h3lib/lib/polyfill.c +++ b/src/h3lib/lib/polyfill.c @@ -418,6 +418,12 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { iter->_started = true; } + if (iter->_polygon->geoloop.numVerts == 0) { + // Nothing can be returned in this case + iterDestroyPolygonCompact(iter); + return; + } + ContainmentMode mode = FLAG_GET_CONTAINMENT_MODE(iter->_flags); while (cell) { @@ -446,6 +452,7 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { // the first polygon vertex, which if it is contained could also // mean we simply intersect. H3Index polygonCell; + // Deferencing verts[0] is safe because we check numVerts above H3Error polygonCellErr = H3_EXPORT(latLngToCell)( &(iter->_polygon->geoloop.verts[0]), cellRes, &polygonCell); if (polygonCellErr != E_SUCCESS) { From 7659732b9e1909e1cf71aa566b4bdf0f5e98404c Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Thu, 14 Dec 2023 16:59:35 -0800 Subject: [PATCH 15/25] add TODO --- src/h3lib/lib/polygon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/h3lib/lib/polygon.c b/src/h3lib/lib/polygon.c index db20bd17c..b09721a39 100644 --- a/src/h3lib/lib/polygon.c +++ b/src/h3lib/lib/polygon.c @@ -130,6 +130,7 @@ bool cellBoundaryInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, // Check for line intersections with, or containment of, any hole for (int i = 0; i < geoPolygon->numHoles; i++) { + // TODO: There may be numVerts = 0 here, which would cause a crash if (pointInsideGeoLoop(&boundaryLoop, boundaryBBox, &geoPolygon->holes[i].verts[0]) || cellBoundaryCrossesGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1], From 4f0341cf5b5cf992af5cb8c541ad1d56b9a21a59 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Fri, 15 Dec 2023 10:59:34 -0800 Subject: [PATCH 16/25] guard against numVerts = 0 for hole check --- src/h3lib/lib/polygon.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/h3lib/lib/polygon.c b/src/h3lib/lib/polygon.c index b09721a39..18f5b858c 100644 --- a/src/h3lib/lib/polygon.c +++ b/src/h3lib/lib/polygon.c @@ -130,11 +130,12 @@ bool cellBoundaryInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, // Check for line intersections with, or containment of, any hole for (int i = 0; i < geoPolygon->numHoles; i++) { - // TODO: There may be numVerts = 0 here, which would cause a crash - if (pointInsideGeoLoop(&boundaryLoop, boundaryBBox, - &geoPolygon->holes[i].verts[0]) || - cellBoundaryCrossesGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1], - boundary, boundaryBBox)) { + // If the hole has no verts, it is not possible to intersect with it. + if (geoPolygon->holes[i].numVerts > 0 && + (pointInsideGeoLoop(&boundaryLoop, boundaryBBox, + &geoPolygon->holes[i].verts[0]) || + cellBoundaryCrossesGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1], + boundary, boundaryBBox))) { return false; } } From c6a21b82f32117aa879808fba5059ac2c64702a7 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Sun, 17 Dec 2023 10:37:48 -0800 Subject: [PATCH 17/25] add empty with null hole test --- .../testapps/testPolygonToCellsExperimental.c | 75 +++++++------------ 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index a754119cf..a94274ff8 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -49,6 +49,8 @@ static GeoPolygon emptyGeoPolygon; static GeoLoop nullGeoLoop = {.numVerts = 0}; static GeoPolygon nullGeoPolygon; +static GeoPolygon emptyGeoPolygonWithNullHole; + static LatLng invalidVerts[] = {{INFINITY, INFINITY}, {-INFINITY, -INFINITY}}; static GeoLoop invalidGeoLoop = {.numVerts = 2, .verts = invalidVerts}; static GeoPolygon invalidGeoPolygon; @@ -153,6 +155,10 @@ SUITE(polygonToCells) { nullGeoPolygon.geoloop = nullGeoLoop; nullGeoPolygon.numHoles = 0; + emptyGeoPolygonWithNullHole.geoloop = emptyGeoLoop; + emptyGeoPolygonWithNullHole.numHoles = 1; + emptyGeoPolygonWithNullHole.holes = &nullGeoLoop; + invalidGeoPolygon.geoloop = invalidGeoLoop; invalidGeoPolygon.numHoles = 0; @@ -306,68 +312,41 @@ SUITE(polygonToCells) { int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (empty center)"); - free(hexagons); - } - - TEST(polygonToCellsNull) { - int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &nullGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); - H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &nullGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); - int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); - - t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (null center)"); - free(hexagons); - } - - TEST(polygonToCellsEmptyContainsOverlapping) { - int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &nullGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); - H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &nullGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); - int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); - - t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (null overlapping)"); + "got expected polygonToCells size (empty)"); free(hexagons); } - TEST(polygonToCellsEmptyFull) { + TEST(polygonToCellsEmptyWithNullHole) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &nullGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); + &emptyGeoPolygonWithNullHole, 9, CONTAINMENT_CENTER, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &nullGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); + &emptyGeoPolygonWithNullHole, 9, CONTAINMENT_CENTER, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (null full)"); + "got expected polygonToCells size (empty with null hole)"); free(hexagons); } - TEST(polygonToCellsEmptyOverlappingBbox) { - int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &nullGeoPolygon, 9, CONTAINMENT_OVERLAPPING_BBOX, &numHexagons)); - H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &nullGeoPolygon, 9, CONTAINMENT_OVERLAPPING_BBOX, hexagons)); - int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); - - t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (null overlapping bbox)"); - free(hexagons); + TEST(polygonToCellsNull) { + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &nullGeoPolygon, 9, flags, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &nullGeoPolygon, 9, flags, hexagons)); + int64_t actualNumIndexes = + countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, + "got expected polygonToCells size (null)"); + free(hexagons); + } } TEST(polygonToCellsContainsPolygon) { From 3975ff942017fe946cfa4384ab487f6bb76ba29a Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Sun, 17 Dec 2023 10:50:47 -0800 Subject: [PATCH 18/25] update test --- .../testapps/testPolygonToCellsExperimental.c | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index a94274ff8..717f4a8ad 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -317,18 +317,35 @@ SUITE(polygonToCells) { } TEST(polygonToCellsEmptyWithNullHole) { - int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &emptyGeoPolygonWithNullHole, 9, CONTAINMENT_CENTER, &numHexagons)); - H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + // Confirm that the same number of cells are returned with and + // without an empty hole + int64_t numHexagonsWithoutHole; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &emptyGeoPolygon, 9, flags, &numHexagonsWithoutHole)); + H3Index *hexagonsWithoutHole = + calloc(numHexagonsWithoutHole, sizeof(H3Index)); - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &emptyGeoPolygonWithNullHole, 9, CONTAINMENT_CENTER, hexagons)); - int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &emptyGeoPolygon, 9, flags, hexagonsWithoutHole)); + int64_t actualNumIndexesWithoutHole = countNonNullIndexes( + hexagonsWithoutHole, numHexagonsWithoutHole); + free(hexagonsWithoutHole); - t_assert(actualNumIndexes == 0, - "got expected polygonToCells size (empty with null hole)"); - free(hexagons); + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &emptyGeoPolygonWithNullHole, 9, flags, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &emptyGeoPolygonWithNullHole, 9, flags, hexagons)); + int64_t actualNumIndexes = + countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == actualNumIndexesWithoutHole, + "got expected polygonToCells size (empty with null hole)"); + free(hexagons); + } } TEST(polygonToCellsNull) { From cfe8816f9682230996f5675b97db8578d8f52052 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Sun, 17 Dec 2023 11:02:43 -0800 Subject: [PATCH 19/25] actually cover --- .../testapps/testPolygonToCellsExperimental.c | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 717f4a8ad..988880e95 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -49,7 +49,7 @@ static GeoPolygon emptyGeoPolygon; static GeoLoop nullGeoLoop = {.numVerts = 0}; static GeoPolygon nullGeoPolygon; -static GeoPolygon emptyGeoPolygonWithNullHole; +static GeoPolygon sfGeoPolygonWithNullHole; static LatLng invalidVerts[] = {{INFINITY, INFINITY}, {-INFINITY, -INFINITY}}; static GeoLoop invalidGeoLoop = {.numVerts = 2, .verts = invalidVerts}; @@ -155,9 +155,9 @@ SUITE(polygonToCells) { nullGeoPolygon.geoloop = nullGeoLoop; nullGeoPolygon.numHoles = 0; - emptyGeoPolygonWithNullHole.geoloop = emptyGeoLoop; - emptyGeoPolygonWithNullHole.numHoles = 1; - emptyGeoPolygonWithNullHole.holes = &nullGeoLoop; + sfGeoPolygonWithNullHole.geoloop = sfGeoLoop; + sfGeoPolygonWithNullHole.numHoles = 1; + sfGeoPolygonWithNullHole.holes = &nullGeoLoop; invalidGeoPolygon.geoloop = invalidGeoLoop; invalidGeoPolygon.numHoles = 0; @@ -316,34 +316,34 @@ SUITE(polygonToCells) { free(hexagons); } - TEST(polygonToCellsEmptyWithNullHole) { + TEST(polygonToCellssfWithNullHole) { for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { // Confirm that the same number of cells are returned with and // without an empty hole int64_t numHexagonsWithoutHole; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &emptyGeoPolygon, 9, flags, &numHexagonsWithoutHole)); + &sfGeoPolygon, 9, flags, &numHexagonsWithoutHole)); H3Index *hexagonsWithoutHole = calloc(numHexagonsWithoutHole, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &emptyGeoPolygon, 9, flags, hexagonsWithoutHole)); + &sfGeoPolygon, 9, flags, hexagonsWithoutHole)); int64_t actualNumIndexesWithoutHole = countNonNullIndexes( hexagonsWithoutHole, numHexagonsWithoutHole); free(hexagonsWithoutHole); int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &emptyGeoPolygonWithNullHole, 9, flags, &numHexagons)); + &sfGeoPolygonWithNullHole, 9, flags, &numHexagons)); H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &emptyGeoPolygonWithNullHole, 9, flags, hexagons)); + &sfGeoPolygonWithNullHole, 9, flags, hexagons)); int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); t_assert(actualNumIndexes == actualNumIndexesWithoutHole, - "got expected polygonToCells size (empty with null hole)"); + "got expected polygonToCells size (sf with null hole)"); free(hexagons); } } From 673046900ad0eabaa65bcd66c508047ce24af806 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Sun, 28 Jan 2024 11:07:49 -0800 Subject: [PATCH 20/25] add fuzzer derived test case --- .../testapps/testPolygonToCellsExperimental.c | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 988880e95..0710f54ed 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -49,6 +49,12 @@ static GeoPolygon emptyGeoPolygon; static GeoLoop nullGeoLoop = {.numVerts = 0}; static GeoPolygon nullGeoPolygon; +static LatLng singleVert[] = { + {-2458342481021883972259660398208400791736363001944288341450980211606536535492675816838520454729626606617401537208948885147545930079455247090683734597891092223711178354749340595418656865312221132096067702544124896034621453367423118181128977366942366333665280.000000, + -2458343740331028994843467164510968055426643495662759512417906485158670133931720348722610840257947672725818463095310525347561848316357826117675135273725409046652087471434926446163500402816566116551621419239229327985229318738203409398420671266791854901821440.000000}}; +static GeoLoop singleVertGeoLoop = {.numVerts = 1, .verts = singleVert}; +static GeoPolygon singleVertGeoPolygon; + static GeoPolygon sfGeoPolygonWithNullHole; static LatLng invalidVerts[] = {{INFINITY, INFINITY}, {-INFINITY, -INFINITY}}; @@ -155,6 +161,9 @@ SUITE(polygonToCells) { nullGeoPolygon.geoloop = nullGeoLoop; nullGeoPolygon.numHoles = 0; + singleVertGeoPolygon.geoloop = singleVertGeoLoop; + singleVertGeoPolygon.numHoles = 0; + sfGeoPolygonWithNullHole.geoloop = sfGeoLoop; sfGeoPolygonWithNullHole.numHoles = 1; sfGeoPolygonWithNullHole.holes = &nullGeoLoop; @@ -366,6 +375,26 @@ SUITE(polygonToCells) { } } + TEST(polygonToCellsSingleVert) { + for (int res = 0; res < MAX_H3_RES; res++) { + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &singleVertGeoPolygon, res, flags, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &singleVertGeoPolygon, res, flags, hexagons)); + int64_t actualNumIndexes = + countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, + "got expected polygonToCells size (null)"); + free(hexagons); + } + } + } + TEST(polygonToCellsContainsPolygon) { int64_t numHexagons; t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( From 29a9b01089ba2fb76bd2cee278921ae96c3d300c Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Sun, 28 Jan 2024 11:09:52 -0800 Subject: [PATCH 21/25] simplify --- src/apps/testapps/testPolygonToCellsExperimental.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 0710f54ed..206cf8a82 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -49,9 +49,7 @@ static GeoPolygon emptyGeoPolygon; static GeoLoop nullGeoLoop = {.numVerts = 0}; static GeoPolygon nullGeoPolygon; -static LatLng singleVert[] = { - {-2458342481021883972259660398208400791736363001944288341450980211606536535492675816838520454729626606617401537208948885147545930079455247090683734597891092223711178354749340595418656865312221132096067702544124896034621453367423118181128977366942366333665280.000000, - -2458343740331028994843467164510968055426643495662759512417906485158670133931720348722610840257947672725818463095310525347561848316357826117675135273725409046652087471434926446163500402816566116551621419239229327985229318738203409398420671266791854901821440.000000}}; +static LatLng singleVert[] = {{-10, 0}}; static GeoLoop singleVertGeoLoop = {.numVerts = 1, .verts = singleVert}; static GeoPolygon singleVertGeoPolygon; @@ -65,6 +63,7 @@ static LatLng invalid2Verts[] = {{NAN, NAN}, {-NAN, -NAN}}; static GeoLoop invalid2GeoLoop = {.numVerts = 2, .verts = invalid2Verts}; static GeoPolygon invalid2GeoPolygon; +// TODO: This is unused static LatLng pointVerts[] = {{0, 0}}; static GeoLoop pointGeoLoop = {.numVerts = 1, .verts = pointVerts}; static GeoPolygon pointGeoPolygon; From 85d5641b7aedf4ff9b37c78c9bc163cdcddd6cad Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Sun, 28 Jan 2024 11:12:03 -0800 Subject: [PATCH 22/25] year --- src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c | 2 +- src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c b/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c index 23a9b43fa..1a87dc4a0 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c +++ b/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c @@ -1,5 +1,5 @@ /* - * Copyright 2023 Uber Technologies, Inc. + * Copyright 2023-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. diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c b/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c index eca247d5e..a507e432c 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c +++ b/src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c @@ -1,5 +1,5 @@ /* - * Copyright 2023 Uber Technologies, Inc. + * Copyright 2023-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. From 5ac48c5fa57560fdf9d08aa22a07d1e758fe7293 Mon Sep 17 00:00:00 2001 From: Isaac Brodsky Date: Sun, 28 Jan 2024 11:14:12 -0800 Subject: [PATCH 23/25] actually pass flags --- src/apps/fuzzers/fuzzerPolygonToCells.c | 2 +- src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/fuzzers/fuzzerPolygonToCells.c b/src/apps/fuzzers/fuzzerPolygonToCells.c index cc6b77da0..84fe863b7 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCells.c +++ b/src/apps/fuzzers/fuzzerPolygonToCells.c @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Uber Technologies, Inc. + * Copyright 2022-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. diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c b/src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c index 546c6eb46..b4e1d47bf 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c +++ b/src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c @@ -52,7 +52,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { geoPolygon.geoloop.verts = (LatLng *)(data + 1); for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { - run(&geoPolygon, 0, res); + run(&geoPolygon, flags, res); } return 0; From 5f44c23e0f1382c82bebabc8abe5ea0fc39df158 Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Sun, 14 Jul 2024 08:42:46 -0700 Subject: [PATCH 24/25] Cleaner test cases, tests for max polyfill size --- .../testapps/testPolygonToCellsExperimental.c | 210 +++++++++++------- 1 file changed, 124 insertions(+), 86 deletions(-) diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 6c824f25e..f8bd7dea3 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -31,49 +31,97 @@ static LatLng sfVerts[] = { {0.659966917655, -2.1364398519396}, {0.6595011102219, -2.1359434279405}, {0.6583348114025, -2.1354884206045}, {0.6581220034068, -2.1382437718946}, {0.6594479998527, -2.1384597563896}, {0.6599990002976, -2.1376771158464}}; -static GeoLoop sfGeoLoop = {.numVerts = 6, .verts = sfVerts}; -static GeoPolygon sfGeoPolygon; -static LatLng holeVerts[] = {{0.6595072188743, -2.1371053983433}, +static GeoPolygon sfGeoPolygon = { + .geoloop = {.numVerts = ARRAY_SIZE(sfVerts), .verts = sfVerts}}; + +static GeoPolygon holeGeoPolygon = { + .geoloop = {.numVerts = ARRAY_SIZE(sfVerts), .verts = sfVerts}, + .numHoles = 1, + .holes = (GeoLoop[]){ + {.numVerts = 3, + .verts = (LatLng[]){{0.6595072188743, -2.1371053983433}, {0.6591482046471, -2.1373141048153}, - {0.6592295020837, -2.1365222838402}}; -static GeoLoop holeGeoLoop = {.numVerts = 3, .verts = holeVerts}; -static GeoPolygon holeGeoPolygon; + {0.6592295020837, -2.1365222838402}}}}}; -static LatLng emptyVerts[] = {{0.659966917655, -2.1364398519394}, - {0.659966917656, -2.1364398519395}, - {0.659966917657, -2.1364398519396}}; -static GeoLoop emptyGeoLoop = {.numVerts = 3, .verts = emptyVerts}; -static GeoPolygon emptyGeoPolygon; +static GeoPolygon emptyGeoPolygon = { + .geoloop = {.numVerts = 3, + .verts = (LatLng[]){{0.659966917655, -2.1364398519394}, + {0.659966917656, -2.1364398519395}, + {0.659966917657, -2.1364398519396}}}}; -static LatLng invalidVerts[] = {{INFINITY, INFINITY}, {-INFINITY, -INFINITY}}; -static GeoLoop invalidGeoLoop = {.numVerts = 2, .verts = invalidVerts}; -static GeoPolygon invalidGeoPolygon; +static GeoPolygon nonFiniteGeoPolygon = { + .geoloop = { + .numVerts = 2, + .verts = (LatLng[]){{INFINITY, INFINITY}, {-INFINITY, -INFINITY}}}}; -static LatLng outOfBoundsVert[] = {{-2000, -2000}}; -static GeoLoop outOfBoundsVertGeoLoop = {.numVerts = 1, - .verts = outOfBoundsVert}; -static GeoPolygon outOfBoundsVertGeoPolygon; +static GeoPolygon nanGeoPolygon = { + .geoloop = {.numVerts = 2, .verts = (LatLng[]){{NAN, NAN}, {-NAN, -NAN}}}}; -static LatLng invalid2Verts[] = {{NAN, NAN}, {-NAN, -NAN}}; -static GeoLoop invalid2GeoLoop = {.numVerts = 2, .verts = invalid2Verts}; -static GeoPolygon invalid2GeoPolygon; +static GeoPolygon outOfBoundsVertGeoPolygon = { + .geoloop = {.numVerts = 1, .verts = (LatLng[]){{-2000, -2000}}}}; -static GeoLoop nullGeoLoop = {.numVerts = 0}; -static GeoPolygon nullGeoPolygon; +static GeoPolygon nullGeoPolygon = {.geoloop = {.numVerts = 0}}; static LatLng pointVerts[] = {{0.6595072188743, -2.1371053983433}}; -static GeoLoop pointGeoLoop = {.numVerts = 1, .verts = pointVerts}; -static GeoPolygon pointGeoPolygon; +static GeoPolygon pointGeoPolygon = { + .geoloop = {.numVerts = ARRAY_SIZE(pointVerts), .verts = pointVerts}}; static LatLng lineVerts[] = {{0.6595072188743, -2.1371053983433}, {0.6591482046471, -2.1373141048153}}; -static GeoLoop lineGeoLoop = {.numVerts = 2, .verts = lineVerts}; -static GeoPolygon lineGeoPolygon; +static GeoPolygon lineGeoPolygon = { + .geoloop = {.numVerts = ARRAY_SIZE(lineVerts), .verts = lineVerts}}; + +static GeoPolygon nullHoleGeoPolygon = { + .geoloop = {.numVerts = ARRAY_SIZE(sfVerts), .verts = sfVerts}, + .numHoles = 1, + .holes = (GeoLoop[]){{.numVerts = 0}}}; + +static GeoPolygon pointHoleGeoPolygon = { + .geoloop = {.numVerts = ARRAY_SIZE(sfVerts), .verts = sfVerts}, + .numHoles = 1, + .holes = + (GeoLoop[]){{.numVerts = ARRAY_SIZE(pointVerts), .verts = pointVerts}}}; +static GeoPolygon lineHoleGeoPolygon = { + .geoloop = {.numVerts = ARRAY_SIZE(sfVerts), .verts = sfVerts}, + .numHoles = 1, + .holes = + (GeoLoop[]){{.numVerts = ARRAY_SIZE(lineVerts), .verts = lineVerts}}}; + +static GeoPolygon fuzzerFailure = { + .geoloop = {.numVerts = 1, .verts = (LatLng[]){{0.000000000, 0.000000000}}}, + .numHoles = 5, + .holes = (GeoLoop[]){ + {.numVerts = 9, + .verts = + (LatLng[]){ + {0.000000000, -0.000000000}, + {-0.000000000, + 2748442886721409313719038754629364176612174103396910975667688045384466158239047111515156119908354033028732640897702389786583276177564900289955662483960102912.000000000}, + {-0.000000000, -0.000000000}, + {-0.000000000, -0.000000000}, + {-0.000000000, -0.000000000}, + {-0.000000000, -0.000000000}, + {-0.000000000, -0.000000000}, + {-0.000000000, 0.000000000}, + {0.000000000, 0.000000000}}}, + {.numVerts = 0, .verts = (LatLng[]){}}, + {.numVerts = 0, .verts = (LatLng[]){}}, + {.numVerts = 0, .verts = (LatLng[]){}}, + {.numVerts = 0, .verts = (LatLng[]){}}}}; -static GeoPolygon nullHoleGeoPolygon; -static GeoPolygon pointHoleGeoPolygon; -static GeoPolygon lineHoleGeoPolygon; +/** + * Count the number of cells a polyfill would return, without allocating + * any memory for them. + */ +static H3Error countPolygonToCellsExperimental(const GeoPolygon *polygon, + int res, uint32_t flags, + int64_t *out) { + IterCellsPolygon iter = iterInitPolygon(polygon, res, flags); + *out = 0; + for (; iter.cell; iterStepPolygon(&iter)) *out += 1; + return iter.error; +} /** * Return true if the cell crosses the meridian. @@ -150,45 +198,30 @@ static void fillIndex_assertions(H3Index h) { } SUITE(polygonToCells) { - sfGeoPolygon.geoloop = sfGeoLoop; - sfGeoPolygon.numHoles = 0; - - holeGeoPolygon.geoloop = sfGeoLoop; - holeGeoPolygon.numHoles = 1; - holeGeoPolygon.holes = &holeGeoLoop; - - nullHoleGeoPolygon.geoloop = sfGeoLoop; - nullHoleGeoPolygon.numHoles = 1; - nullHoleGeoPolygon.holes = &nullGeoLoop; - - pointHoleGeoPolygon.geoloop = sfGeoLoop; - pointHoleGeoPolygon.numHoles = 1; - pointHoleGeoPolygon.holes = &pointGeoLoop; - - lineHoleGeoPolygon.geoloop = sfGeoLoop; - lineHoleGeoPolygon.numHoles = 1; - lineHoleGeoPolygon.holes = &lineGeoLoop; - - emptyGeoPolygon.geoloop = emptyGeoLoop; - emptyGeoPolygon.numHoles = 0; - - invalidGeoPolygon.geoloop = invalidGeoLoop; - invalidGeoPolygon.numHoles = 0; - - invalid2GeoPolygon.geoloop = invalid2GeoLoop; - invalid2GeoPolygon.numHoles = 0; - - outOfBoundsVertGeoPolygon.geoloop = outOfBoundsVertGeoLoop; - outOfBoundsVertGeoPolygon.numHoles = 0; - - nullGeoPolygon.geoloop = nullGeoLoop; - nullGeoPolygon.numHoles = 0; - - pointGeoPolygon.geoloop = pointGeoLoop; - pointGeoPolygon.numHoles = 0; - - lineGeoPolygon.geoloop = lineGeoLoop; - lineGeoPolygon.numHoles = 0; + TEST(maxPolygonToCellsSize) { + GeoPolygon testPolygon[] = { + sfGeoPolygon, holeGeoPolygon, nullGeoPolygon, + lineGeoPolygon, pointGeoPolygon, pointHoleGeoPolygon, + nanGeoPolygon, nonFiniteGeoPolygon, fuzzerFailure}; + for (int i = 0; i < ARRAY_SIZE(testPolygon); i++) { + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + for (int res = 0; res < 11; res++) { + int64_t estimate; + t_assertSuccess( + H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &testPolygon[i], 9, flags, &estimate)); + + int64_t exact; + t_assertSuccess(H3_EXPORT(countPolygonToCellsExperimental)( + &testPolygon[i], 9, flags, &exact)); + + printf("exact: %lld | estimate: %lld\n", exact, estimate); + t_assert(exact <= estimate, + "estimate is sufficently large for polyfill"); + } + } + } + } TEST(polygonToCells_CenterContainment) { int64_t numHexagons; @@ -685,23 +718,28 @@ SUITE(polygonToCells) { free(hexagons); } - TEST(polygonToCellsNullPolygon) { - for (int res = 0; res < MAX_H3_RES; res++) { - for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { - int64_t numHexagons; - t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( - &nullGeoPolygon, res, flags, &numHexagons)); - t_assert(numHexagons == 0, "got expected estimated size"); - H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); - - t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( - &nullGeoPolygon, res, flags, hexagons)); - int64_t actualNumIndexes = - countNonNullIndexes(hexagons, numHexagons); - - t_assert(actualNumIndexes == 0, - "got expected polygonToCells size"); - free(hexagons); + TEST(polygonToCellsInvalidPolygon) { + GeoPolygon invalidPolys[] = {nullGeoPolygon, nonFiniteGeoPolygon, + nanGeoPolygon}; + for (int i = 0; i < ARRAY_SIZE(invalidPolys); i++) { + for (int res = 0; res < MAX_H3_RES; res++) { + for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + int64_t numHexagons; + t_assertSuccess( + H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &invalidPolys[i], res, flags, &numHexagons)); + t_assert(numHexagons == 0, "got expected estimated size"); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &invalidPolys[i], res, flags, hexagons)); + int64_t actualNumIndexes = + countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, + "got expected polygonToCells size"); + free(hexagons); + } } } } From ac8256115813160fcdfec3076843f3e74e9af30a Mon Sep 17 00:00:00 2001 From: Nick Rabinowitz Date: Sun, 14 Jul 2024 08:46:20 -0700 Subject: [PATCH 25/25] WIP on fixing fuzzer issues --- src/apps/applib/include/utility.h | 1 + src/apps/applib/lib/utility.c | 36 +++++++++++++++++ .../fuzzerPolygonToCellsExperimental.c | 39 +++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/src/apps/applib/include/utility.h b/src/apps/applib/include/utility.h index dcf89e7d1..089334162 100644 --- a/src/apps/applib/include/utility.h +++ b/src/apps/applib/include/utility.h @@ -49,6 +49,7 @@ void cellBoundaryPrint(const CellBoundary *b); void cellBoundaryPrintln(const CellBoundary *b); void bboxPrint(const BBox *bbox); void bboxPrintln(const BBox *bbox); +void geoPolygonPrintln(const GeoPolygon *geoPolygon); void randomGeo(LatLng *p); diff --git a/src/apps/applib/lib/utility.c b/src/apps/applib/lib/utility.c index 15a80cd17..4d5d5b250 100644 --- a/src/apps/applib/lib/utility.c +++ b/src/apps/applib/lib/utility.c @@ -130,6 +130,42 @@ void bboxPrintln(const BBox *bbox) { printf("\n"); } +static void geoLoopPrint(const GeoLoop *loop) { + printf("{\n"); + printf(" .numVerts = %d", loop->numVerts); + if (loop->numVerts > 0) { + printf(", .verts = (LatLng[]){\n"); + for (int v = 0; v < loop->numVerts; v++) { + printf("{%.4lf, %.4lf}", loop->verts[v].lat, loop->verts[v].lng); + if (v < loop->numVerts - 1) { + printf(",\n"); + } + } + printf("}"); + } + printf("}"); +} + +void geoPolygonPrintln(const GeoPolygon *geoPolygon) { + printf("{\n"); + printf(" .geoloop = "); + geoLoopPrint(&(geoPolygon->geoloop)); + printf(",\n"); + printf(" .numHoles = %d", geoPolygon->numHoles); + if (geoPolygon->numHoles > 0) { + printf(",\n .holes = (GeoLoop[]){"); + for (int i = 0; i < geoPolygon->numHoles; i++) { + geoLoopPrint(&(geoPolygon->holes[i])); + if (i < geoPolygon->numHoles - 1) { + printf(","); + } + } + printf("}\n"); + } + + printf("}\n"); +} + /** * Apply callback for every unidirectional edge at the given resolution. */ diff --git a/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c b/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c index 1a87dc4a0..c45299529 100644 --- a/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c +++ b/src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c @@ -36,6 +36,28 @@ const int MAX_RES = 15; const int MAX_SZ = 4000000; const int MAX_HOLES = 100; +static GeoPolygon fuzzerFailure = { + .geoloop = {.numVerts = 1, .verts = (LatLng[]){{0.000000, 0.000000}}}, + .numHoles = 5, + .holes = (GeoLoop[]){ + {.numVerts = 9, + .verts = + (LatLng[]){ + {0.000000, -0.000000}, + {-0.000000, + 2748442886721409313719038754629364176612174103396910975667688045384466158239047111515156119908354033028732640897702389786583276177564900289955662483960102912.000000}, + {-0.000000, -0.000000}, + {-0.000000, -0.000000}, + {-0.000000, -0.000000}, + {-0.000000, -0.000000}, + {-0.000000, -0.000000}, + {-0.000000, 0.000000}, + {0.000000, 0.000000}}}, + {.numVerts = 0}, + {.numVerts = 0}, + {.numVerts = 0}, + {.numVerts = 0}}}; + int populateGeoLoop(GeoLoop *g, const uint8_t *data, size_t *offset, size_t size) { if (size < *offset + sizeof(int)) { @@ -52,10 +74,26 @@ int populateGeoLoop(GeoLoop *g, const uint8_t *data, size_t *offset, return 0; } +static H3Error countPolygonToCellsExperimental(const GeoPolygon *polygon, + int res, uint32_t flags, + int64_t *out) { + IterCellsPolygon iter = iterInitPolygon(polygon, res, flags); + *out = 0; + for (; iter.cell; iterStepPolygon(&iter)) *out += 1; + return iter.error; +} + void run(GeoPolygon *geoPolygon, uint32_t flags, int res) { int64_t sz; H3Error err = H3_EXPORT(maxPolygonToCellsSizeExperimental)(geoPolygon, res, flags, &sz); + + geoPolygonPrintln(geoPolygon); + printf("estimate: %lld\n", sz); + int64_t exact; + H3_EXPORT(countPolygonToCellsExperimental)(geoPolygon, res, flags, &exact); + printf("exact: %lld\n", exact); + if (!err && sz < MAX_SZ) { H3Index *out = calloc(sz, sizeof(H3Index)); H3_EXPORT(polygonToCellsExperimental)(geoPolygon, res, flags, out); @@ -93,6 +131,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { } for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) { + run(&fuzzerFailure, flags, res); geoPolygon.numHoles = originalNumHoles; run(&geoPolygon, flags, res); geoPolygon.numHoles = 0;