Skip to content

Commit c75a49f

Browse files
committed
Add more tests for geodesic cells
1 parent d2756ff commit c75a49f

1 file changed

Lines changed: 177 additions & 0 deletions

File tree

src/apps/testapps/testGeodesicGridDiskRoundtrip.c

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,31 @@ static int traceMissingCellAgainstPolygon(const GeoPolygon *polygon,
180180
return 0;
181181
}
182182

183+
/* ------------------------------------------------------------------ */
184+
/* Helper: build the outer polygon for gridDisk(center, k). */
185+
/* Returns E_SUCCESS and populates *mpoly on success. */
186+
/* Caller must call destroyGeoMultiPolygon on E_SUCCESS. */
187+
/* ------------------------------------------------------------------ */
188+
static H3Error buildDiskPolygon(H3Index center, int k, GeoMultiPolygon *mpoly) {
189+
int64_t diskMax = 0;
190+
H3Error err = H3_EXPORT(maxGridDiskSize)(k, &diskMax);
191+
if (err != E_SUCCESS) return err;
192+
193+
H3Index *disk = calloc((size_t)diskMax, sizeof(H3Index));
194+
if (!disk) return E_MEMORY_ALLOC;
195+
196+
err = H3_EXPORT(gridDisk)(center, k, disk);
197+
if (err != E_SUCCESS) {
198+
free(disk);
199+
return err;
200+
}
201+
202+
const int64_t diskCount = compactNonZeroCells(disk, diskMax);
203+
err = H3_EXPORT(cellsToMultiPolygon)(disk, diskCount, mpoly);
204+
free(disk);
205+
return err;
206+
}
207+
183208
/* ------------------------------------------------------------------ */
184209
/* Helper: run the roundtrip check for one (cell, k) pair. */
185210
/* Updates aggregate stats instead of asserting internally. */
@@ -340,4 +365,156 @@ SUITE(GeodesicGridDiskRoundtrip) {
340365
t_assert(total.missingCells == 0,
341366
"all exhaustive res/k configs should round-trip");
342367
}
368+
369+
// Stage 1: for each res-0 and res-1 cell, build the k=1 disk polygon
370+
// (the union of the cell and its 6 neighbours) and run FULL geodesic
371+
// polyfill. The center cell has no edges on the outer polygon boundary, so
372+
// it must appear in the FULL result.
373+
TEST(ringPolygonFullContainment) {
374+
const int resolutions[] = {0, 1};
375+
for (int ri = 0; ri < 2; ri++) {
376+
int res = resolutions[ri];
377+
for (IterCellsResolution it = iterInitRes(res); it.h;
378+
iterStepRes(&it)) {
379+
H3Index center = it.h;
380+
381+
GeoMultiPolygon mpoly = {0};
382+
if (buildDiskPolygon(center, 1, &mpoly) != E_SUCCESS) continue;
383+
if (mpoly.numPolygons != 1 || mpoly.polygons[0].numHoles != 0) {
384+
H3_EXPORT(destroyGeoMultiPolygon)(&mpoly);
385+
continue;
386+
}
387+
388+
uint32_t flags = CONTAINMENT_FULL;
389+
FLAG_SET_GEODESIC(flags);
390+
391+
int64_t outMax = 0;
392+
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)(
393+
&mpoly.polygons[0], res, flags, &outMax));
394+
395+
H3Index *out =
396+
outMax > 0 ? calloc((size_t)outMax, sizeof(H3Index)) : NULL;
397+
t_assert(outMax == 0 || out != NULL, "alloc succeeded");
398+
t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(
399+
&mpoly.polygons[0], res, flags, outMax, out));
400+
401+
bool found = false;
402+
for (int64_t i = 0; i < outMax && !found; i++) {
403+
if (out[i] == center) found = true;
404+
}
405+
t_assert(found,
406+
"center cell must be in FULL result of k=1 disk "
407+
"polygon");
408+
409+
free(out);
410+
H3_EXPORT(destroyGeoMultiPolygon)(&mpoly);
411+
}
412+
}
413+
}
414+
415+
// Stage 2: same k=1 disk polygon but with a hole punched through the
416+
// center cell. The hole vertices are midpoints between each center-cell
417+
// boundary vertex and the cell center, so the hole lies entirely inside
418+
// the center cell.
419+
//
420+
// Expected behaviour:
421+
// FULL – center cell excluded (its center is inside the hole, so
422+
// point-in-polygon returns false and FULL skips it)
423+
// OVERLAPPING – center cell included (the hole's first vertex maps to
424+
// the center cell, detected by the hole-vertex fallback)
425+
// – all 6 neighbours included (their centers are inside the
426+
// filled region)
427+
TEST(ringPolygonHoleExcludes) {
428+
for (IterCellsResolution it = iterInitRes(0); it.h; iterStepRes(&it)) {
429+
H3Index center = it.h;
430+
int res = H3_GET_RESOLUTION(center);
431+
432+
GeoMultiPolygon mpoly = {0};
433+
if (buildDiskPolygon(center, 1, &mpoly) != E_SUCCESS) continue;
434+
if (mpoly.numPolygons != 1 || mpoly.polygons[0].numHoles != 0) {
435+
H3_EXPORT(destroyGeoMultiPolygon)(&mpoly);
436+
continue;
437+
}
438+
439+
// Build the k=1 disk cells list to verify OVERLAPPING later.
440+
int64_t diskMax = 0;
441+
t_assertSuccess(H3_EXPORT(maxGridDiskSize)(1, &diskMax));
442+
H3Index *disk = calloc((size_t)diskMax, sizeof(H3Index));
443+
t_assert(disk != NULL, "alloc disk succeeded");
444+
t_assertSuccess(H3_EXPORT(gridDisk)(center, 1, disk));
445+
const int64_t diskCount = compactNonZeroCells(disk, diskMax);
446+
447+
// Build hole: midpoints between boundary vertices and cell center,
448+
// with longitude wrapped to [-pi, pi] to handle transmeridian
449+
// cells.
450+
CellBoundary bnd = {0};
451+
t_assertSuccess(H3_EXPORT(cellToBoundary)(center, &bnd));
452+
LatLng cellCenter;
453+
t_assertSuccess(H3_EXPORT(cellToLatLng)(center, &cellCenter));
454+
455+
LatLng holeVerts[MAX_CELL_BNDRY_VERTS];
456+
for (int i = 0; i < bnd.numVerts; i++) {
457+
double dlng = bnd.verts[i].lng - cellCenter.lng;
458+
if (dlng > M_PI) dlng -= 2.0 * M_PI;
459+
if (dlng < -M_PI) dlng += 2.0 * M_PI;
460+
holeVerts[i].lat = (bnd.verts[i].lat + cellCenter.lat) / 2.0;
461+
holeVerts[i].lng = cellCenter.lng + dlng / 2.0;
462+
}
463+
GeoLoop holeLoop = {.numVerts = bnd.numVerts, .verts = holeVerts};
464+
465+
// Attach the hole to the outer polygon.
466+
GeoPolygon polyWithHole = mpoly.polygons[0];
467+
polyWithHole.numHoles = 1;
468+
polyWithHole.holes = &holeLoop;
469+
470+
uint32_t flagsFull = CONTAINMENT_FULL;
471+
FLAG_SET_GEODESIC(flagsFull);
472+
uint32_t flagsOver = CONTAINMENT_OVERLAPPING;
473+
FLAG_SET_GEODESIC(flagsOver);
474+
475+
// FULL: center cell must NOT be in result.
476+
int64_t fullMax = 0;
477+
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)(
478+
&polyWithHole, res, flagsFull, &fullMax));
479+
H3Index *fullOut =
480+
fullMax > 0 ? calloc((size_t)fullMax, sizeof(H3Index)) : NULL;
481+
t_assert(fullMax == 0 || fullOut != NULL, "alloc full succeeded");
482+
t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(
483+
&polyWithHole, res, flagsFull, fullMax, fullOut));
484+
485+
bool inFull = false;
486+
for (int64_t i = 0; i < fullMax && !inFull; i++) {
487+
if (fullOut[i] == center) inFull = true;
488+
}
489+
t_assert(!inFull,
490+
"center cell must NOT appear in FULL result when hole "
491+
"is inside it");
492+
free(fullOut);
493+
494+
// OVERLAPPING: every k=1 disk cell (including center) must be in
495+
// result.
496+
int64_t overMax = 0;
497+
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)(
498+
&polyWithHole, res, flagsOver, &overMax));
499+
H3Index *overOut =
500+
overMax > 0 ? calloc((size_t)overMax, sizeof(H3Index)) : NULL;
501+
t_assert(overMax == 0 || overOut != NULL, "alloc over succeeded");
502+
t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(
503+
&polyWithHole, res, flagsOver, overMax, overOut));
504+
505+
for (int64_t i = 0; i < diskCount; i++) {
506+
bool found = false;
507+
for (int64_t j = 0; j < overMax && !found; j++) {
508+
if (overOut[j] == disk[i]) found = true;
509+
}
510+
t_assert(found,
511+
"every k=1 disk cell must be in OVERLAPPING result "
512+
"even with hole inside center cell");
513+
}
514+
free(overOut);
515+
516+
free(disk);
517+
H3_EXPORT(destroyGeoMultiPolygon)(&mpoly);
518+
}
519+
}
343520
}

0 commit comments

Comments
 (0)