Skip to content

Commit

Permalink
Add fixes and tests for 0-, 1-, and 2-vertex geoloops and holes (#816)
Browse files Browse the repository at this point in the history
This includes the fixes from #800, plus one more fix for estimating a 1-vertex polygon. Includes the following tests:

0-vertex polygon, all modes
1-vertex polygon, all modes
2-vertex polygon, all modes
polygon with 0-vertex hole, all modes
polygon with 1-vertex hole, all modes
polygon with 2-vertex hole, all modes
  • Loading branch information
nrabinowitz authored Feb 9, 2024
1 parent 8207a9c commit b57df8b
Show file tree
Hide file tree
Showing 3 changed files with 300 additions and 6 deletions.
279 changes: 277 additions & 2 deletions src/apps/testapps/testPolygonToCellsExperimental.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;

Expand All @@ -153,6 +173,9 @@ SUITE(polygonToCells) {
invalid2GeoPolygon.geoloop = invalid2GeoLoop;
invalid2GeoPolygon.numHoles = 0;

nullGeoPolygon.geoloop = nullGeoLoop;
nullGeoPolygon.numHoles = 0;

pointGeoPolygon.geoloop = pointGeoLoop;
pointGeoPolygon.numHoles = 0;

Expand Down Expand Up @@ -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++) {
Expand Down
17 changes: 17 additions & 0 deletions src/h3lib/lib/polyfill.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 6 additions & 4 deletions src/h3lib/lib/polygon.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down

0 comments on commit b57df8b

Please sign in to comment.