Skip to content

Commit 8ad1bcd

Browse files
Add unit tests for non-contiguous polygonToCells edge cases (#600)
1 parent 8085979 commit 8ad1bcd

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The public API of this library consists of the functions declared in file
77

88
## [Unreleased]
99
### Added
10+
- Unit tests for non-contiguous `polygonToCells` edge cases (#600)
1011
- `reverseDirectedEdge` function (#1098)
1112
- (internal) `geoLoopArea` function (#1101)
1213

src/apps/testapps/testPolygonToCells.c

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,4 +516,112 @@ SUITE(polygonToCells) {
516516
free(found);
517517
free(search);
518518
}
519+
520+
TEST(polygonToCellsBarbell) {
521+
// Two lobes connected by a bridge thinner than a hexagon
522+
LatLng verts[] = {
523+
{0.010, 0.000}, {0.010, 0.010}, {0.000, 0.010}, // Lobe 1
524+
{0.0051, 0.010}, {0.0051, 0.050}, // Bridge top
525+
{0.000, 0.050}, {0.000, 0.060}, {0.010, 0.060}, // Lobe 2
526+
{0.010, 0.050}, // Lobe 2
527+
{0.0050, 0.050}, {0.0050, 0.010}, // Bridge bottom
528+
{0.000, 0.000} // Close
529+
};
530+
GeoLoop loop = {.numVerts = 12, .verts = verts};
531+
GeoPolygon polygon = {.geoloop = loop, .numHoles = 0, .holes = NULL};
532+
533+
int res = 9;
534+
int64_t numHexagons;
535+
t_assertSuccess(
536+
H3_EXPORT(maxPolygonToCellsSize)(&polygon, res, 0, &numHexagons));
537+
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
538+
539+
t_assertSuccess(H3_EXPORT(polygonToCells)(&polygon, res, 0, hexagons));
540+
int64_t count = countNonNullIndexes(hexagons, numHexagons);
541+
542+
// Expect hexes in both lobes. If it was a flood-fill, it would only
543+
// find ~half.
544+
t_assert(count > 1000, "Should find hexagons in both barbell lobes");
545+
free(hexagons);
546+
}
547+
548+
TEST(polygonToCellsSplittingHole) {
549+
// A square with a hole that almost cuts it in half
550+
LatLng outerVerts[] = {
551+
{0.0, 0.0}, {0.0, 0.08}, {0.08, 0.08}, {0.08, 0.0}};
552+
GeoLoop outerLoop = {.numVerts = 4, .verts = outerVerts};
553+
554+
LatLng holeVerts[] = {
555+
{0.0001, 0.03}, {0.0799, 0.03}, {0.0799, 0.05}, {0.0001, 0.05}};
556+
GeoLoop holeLoop = {.numVerts = 4, .verts = holeVerts};
557+
558+
GeoPolygon polygon = {
559+
.geoloop = outerLoop, .numHoles = 1, .holes = &holeLoop};
560+
561+
int res = 9;
562+
int64_t numHexagons;
563+
t_assertSuccess(
564+
H3_EXPORT(maxPolygonToCellsSize)(&polygon, res, 0, &numHexagons));
565+
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
566+
567+
t_assertSuccess(H3_EXPORT(polygonToCells)(&polygon, res, 0, hexagons));
568+
int64_t count = countNonNullIndexes(hexagons, numHexagons);
569+
570+
t_assert(count > 500,
571+
"Should find hexagons on both sides of a splitting hole");
572+
free(hexagons);
573+
}
574+
575+
TEST(polygonToCellsMultiIslandSerpent) {
576+
// A winding shape that results in 3+ disconnected cell clusters
577+
LatLng verts[] = {
578+
{0.00, 0.00}, {0.01, 0.00}, {0.01, 0.01}, // Island 1
579+
{0.0051, 0.01}, {0.0051, 0.02}, // Neck 1
580+
{0.00, 0.02}, {0.00, 0.03}, {0.01, 0.03}, // Island 2
581+
{0.0051, 0.03}, {0.0051, 0.04}, // Neck 2
582+
{0.00, 0.04}, {0.00, 0.05}, {0.01, 0.05}, // Island 3
583+
{0.01, 0.06}, {-0.01, 0.06}, // Back wall
584+
{-0.01, 0.00} // Close
585+
};
586+
GeoLoop loop = {.numVerts = 16, .verts = verts};
587+
GeoPolygon polygon = {.geoloop = loop, .numHoles = 0, .holes = NULL};
588+
589+
int res = 9;
590+
int64_t numHexagons;
591+
t_assertSuccess(
592+
H3_EXPORT(maxPolygonToCellsSize)(&polygon, res, 0, &numHexagons));
593+
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
594+
595+
t_assertSuccess(H3_EXPORT(polygonToCells)(&polygon, res, 0, hexagons));
596+
int64_t count = countNonNullIndexes(hexagons, numHexagons);
597+
598+
t_assert(count > 1500, "Should find hexagons in all 3 serpent islands");
599+
free(hexagons);
600+
}
601+
602+
TEST(polygonToCellsTransmeridianBarbell) {
603+
// Barbell shape crossing the 180/-180 meridian
604+
double east = M_PI - 0.001;
605+
double west = -M_PI + 0.001;
606+
607+
LatLng verts[] = {{0.01, east}, {0.01, M_PI}, {0.00, M_PI},
608+
{0.0051, M_PI}, {0.0051, -M_PI}, {0.00, -M_PI},
609+
{0.00, west}, {0.01, west}, {0.01, -M_PI},
610+
{0.0050, -M_PI}, {0.0050, M_PI}, {0.00, east}};
611+
GeoLoop loop = {.numVerts = 12, .verts = verts};
612+
GeoPolygon polygon = {.geoloop = loop, .numHoles = 0, .holes = NULL};
613+
614+
int res = 9;
615+
int64_t numHexagons;
616+
t_assertSuccess(
617+
H3_EXPORT(maxPolygonToCellsSize)(&polygon, res, 0, &numHexagons));
618+
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
619+
620+
t_assertSuccess(H3_EXPORT(polygonToCells)(&polygon, res, 0, hexagons));
621+
int64_t count = countNonNullIndexes(hexagons, numHexagons);
622+
623+
t_assert(count > 10,
624+
"Should find hexagons on both sides of antimeridian");
625+
free(hexagons);
626+
}
519627
}

0 commit comments

Comments
 (0)