diff --git a/src/apps/testapps/testPolygonToCellsExperimental.c b/src/apps/testapps/testPolygonToCellsExperimental.c index 4d48c3c1d..654de0c4f 100644 --- a/src/apps/testapps/testPolygonToCellsExperimental.c +++ b/src/apps/testapps/testPolygonToCellsExperimental.c @@ -54,14 +54,22 @@ static LatLng invalid2Verts[] = {{NAN, NAN}, {-NAN, -NAN}}; static GeoLoop invalid2GeoLoop = {.numVerts = 2, .verts = invalid2Verts}; static GeoPolygon invalid2GeoPolygon; -static LatLng pointVerts[] = {{0, 0}}; +static GeoLoop nullGeoLoop = {.numVerts = 0}; +static GeoPolygon nullGeoPolygon; + +static LatLng pointVerts[] = {{0.6595072188743, -2.1371053983433}}; static GeoLoop pointGeoLoop = {.numVerts = 1, .verts = pointVerts}; static GeoPolygon pointGeoPolygon; -static LatLng lineVerts[] = {{0, 0}, {1, 0}}; +static LatLng lineVerts[] = {{0.6595072188743, -2.1371053983433}, + {0.6591482046471, -2.1373141048153}}; static GeoLoop lineGeoLoop = {.numVerts = 2, .verts = lineVerts}; static GeoPolygon lineGeoPolygon; +static GeoPolygon nullHoleGeoPolygon; +static GeoPolygon pointHoleGeoPolygon; +static GeoPolygon lineHoleGeoPolygon; + /** * Return true if the cell crosses the meridian. */ @@ -144,6 +152,18 @@ SUITE(polygonToCells) { 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; @@ -153,6 +173,9 @@ SUITE(polygonToCells) { invalid2GeoPolygon.geoloop = invalid2GeoLoop; invalid2GeoPolygon.numHoles = 0; + nullGeoPolygon.geoloop = nullGeoLoop; + nullGeoPolygon.numHoles = 0; + pointGeoPolygon.geoloop = pointGeoLoop; pointGeoPolygon.numHoles = 0; @@ -598,6 +621,258 @@ 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(polygonToCellsPointPolygon) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &pointGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); + t_assert(numHexagons == 1, "got expected estimated size"); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &pointGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, "got expected polygonToCells size"); + free(hexagons); + } + + TEST(polygonToCellsPointPolygon_full) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &pointGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); + t_assert(numHexagons == 1, "got expected estimated size"); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &pointGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, "got expected polygonToCells size"); + free(hexagons); + } + + TEST(polygonToCellsPointPolygon_overlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &pointGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); + t_assert(numHexagons == 1, "got expected estimated size"); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &pointGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 1, "got expected polygonToCells size"); + free(hexagons); + } + + TEST(polygonToCellsLinePolygon) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &lineGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &lineGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, "got expected polygonToCells size"); + free(hexagons); + } + + TEST(polygonToCellsLinePolygon_full) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &lineGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &lineGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 0, "got expected polygonToCells size"); + free(hexagons); + } + + TEST(polygonToCellsLinePolygon_overlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &lineGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &lineGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + t_assert(actualNumIndexes == 9, "got expected polygonToCells size"); + free(hexagons); + } + + TEST(polygonToCellsNullHole) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &nullHoleGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &nullHoleGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // Same as without the hole + t_assert(actualNumIndexes == 1253, + "got expected polygonToCells size (null hole)"); + free(hexagons); + } + + TEST(polygonToCellsNullHole_full) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &nullHoleGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &nullHoleGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // Same as without the hole + t_assert(actualNumIndexes == 1175, + "got expected polygonToCells size (null hole)"); + free(hexagons); + } + + TEST(polygonToCellsNullHole_overlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &nullHoleGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &nullHoleGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // Same as without the hole + t_assert(actualNumIndexes == 1334, + "got expected polygonToCells size (null hole)"); + free(hexagons); + } + + TEST(polygonToCellsPointHole) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &pointHoleGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &pointHoleGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // Same as without the hole + t_assert(actualNumIndexes == 1253, + "got expected polygonToCells size (point hole)"); + free(hexagons); + } + + TEST(polygonToCellsPointHole_full) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &pointHoleGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &pointHoleGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // We expect that the cell containing the hole is not included + t_assert(actualNumIndexes == 1175 - 1, + "got expected polygonToCells size (point hole)"); + free(hexagons); + } + + TEST(polygonToCellsPointHole_overlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &pointHoleGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &pointHoleGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // Same as without the hole + t_assert(actualNumIndexes == 1334, + "got expected polygonToCells size (point hole)"); + free(hexagons); + } + + TEST(polygonToCellsLineHole) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &lineHoleGeoPolygon, 9, CONTAINMENT_CENTER, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &lineHoleGeoPolygon, 9, CONTAINMENT_CENTER, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // Same as without the hole + t_assert(actualNumIndexes == 1253, + "got expected polygonToCells size (line hole)"); + free(hexagons); + } + + TEST(polygonToCellsLineHole_full) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &lineHoleGeoPolygon, 9, CONTAINMENT_FULL, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &lineHoleGeoPolygon, 9, CONTAINMENT_FULL, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // We expect that the cells intersecting the line are not included + t_assert(actualNumIndexes == 1175 - 9, + "got expected polygonToCells size (line hole)"); + free(hexagons); + } + + TEST(polygonToCellsLineHole_overlapping) { + int64_t numHexagons; + t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)( + &lineHoleGeoPolygon, 9, CONTAINMENT_OVERLAPPING, &numHexagons)); + H3Index *hexagons = calloc(numHexagons, sizeof(H3Index)); + + t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)( + &lineHoleGeoPolygon, 9, CONTAINMENT_OVERLAPPING, hexagons)); + int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons); + + // Same as without the hole + t_assert(actualNumIndexes == 1334, + "got expected polygonToCells size (line hole)"); + free(hexagons); + } + TEST(invalidFlags) { int64_t numHexagons; for (uint32_t flags = CONTAINMENT_INVALID; flags <= 32; flags++) { diff --git a/src/h3lib/lib/polyfill.c b/src/h3lib/lib/polyfill.c index 688cb1529..7b771f22d 100644 --- a/src/h3lib/lib/polyfill.c +++ b/src/h3lib/lib/polyfill.c @@ -418,6 +418,12 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) { iter->_started = true; } + // Short-circuit iteration for 0-vert polygon + if (iter->_polygon->geoloop.numVerts == 0) { + iterDestroyPolygonCompact(iter); + return; + } + ContainmentMode mode = FLAG_GET_CONTAINMENT_MODE(iter->_flags); while (cell) { @@ -700,6 +706,17 @@ static double getAverageCellArea(int res) { H3Error H3_EXPORT(maxPolygonToCellsSizeExperimental)(const GeoPolygon *polygon, int res, uint32_t flags, int64_t *out) { + // Special case: 0-vertex polygon + if (polygon->geoloop.numVerts == 0) { + *out = 0; + return E_SUCCESS; + } + // Special case: 1-vertex polygon + if (polygon->geoloop.numVerts == 1) { + *out = 1; + return E_SUCCESS; + } + // Initialize the iterator without stepping, so we can adjust the res and // flags (after they are validated by the initialization) before we start IterCellsPolygonCompact iter = _iterInitPolygonCompact(polygon, res, flags); diff --git a/src/h3lib/lib/polygon.c b/src/h3lib/lib/polygon.c index db20bd17c..6119ee7d7 100644 --- a/src/h3lib/lib/polygon.c +++ b/src/h3lib/lib/polygon.c @@ -130,10 +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++) { - 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 + 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; } }