src/apps/testapps/testPolygonToCells.c (387 lines of code) (raw):
/*
* Copyright 2017-2018, 2020-2021 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.
*/
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include "algos.h"
#include "constants.h"
#include "h3Index.h"
#include "latLng.h"
#include "polygon.h"
#include "test.h"
#include "utility.h"
// Fixtures
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},
{0.6591482046471, -2.1373141048153},
{0.6592295020837, -2.1365222838402}};
static GeoLoop holeGeoLoop = {.numVerts = 3, .verts = holeVerts};
static GeoPolygon holeGeoPolygon;
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 LatLng invalidVerts[] = {{INFINITY, INFINITY}, {-INFINITY, -INFINITY}};
static GeoLoop invalidGeoLoop = {.numVerts = 2, .verts = invalidVerts};
static GeoPolygon invalidGeoPolygon;
static LatLng invalid2Verts[] = {{NAN, NAN}, {-NAN, -NAN}};
static GeoLoop invalid2GeoLoop = {.numVerts = 2, .verts = invalid2Verts};
static GeoPolygon invalid2GeoPolygon;
static LatLng pointVerts[] = {{0, 0}};
static GeoLoop pointGeoLoop = {.numVerts = 1, .verts = pointVerts};
static GeoPolygon pointGeoPolygon;
static LatLng lineVerts[] = {{0, 0}, {1, 0}};
static GeoLoop lineGeoLoop = {.numVerts = 2, .verts = lineVerts};
static GeoPolygon lineGeoPolygon;
/**
* Return true if the cell crosses the meridian.
*/
static bool isTransmeridianCell(H3Index h) {
CellBoundary bndry;
H3_EXPORT(cellToBoundary)(h, &bndry);
double minLng = M_PI, maxLng = -M_PI;
for (int i = 0; i < bndry.numVerts; i++) {
if (bndry.verts[i].lng < minLng) minLng = bndry.verts[i].lng;
if (bndry.verts[i].lng > maxLng) maxLng = bndry.verts[i].lng;
}
return maxLng - minLng > M_PI - (M_PI / 4);
}
static void fillIndex_assertions(H3Index h) {
if (isTransmeridianCell(h)) {
// TODO: these do not work correctly
return;
}
int currentRes = H3_EXPORT(getResolution)(h);
// TODO: Not testing more than one depth because the assertions fail.
for (int nextRes = currentRes; nextRes <= currentRes + 1; nextRes++) {
CellBoundary bndry;
H3_EXPORT(cellToBoundary)(h, &bndry);
GeoPolygon polygon = {
.geoloop = {.numVerts = bndry.numVerts, .verts = bndry.verts},
.numHoles = 0,
.holes = 0};
int64_t polygonToCellsSize;
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&polygon, nextRes, 0,
&polygonToCellsSize));
H3Index *polygonToCellsOut =
calloc(polygonToCellsSize, sizeof(H3Index));
t_assertSuccess(
H3_EXPORT(polygonToCells)(&polygon, nextRes, 0, polygonToCellsOut));
int64_t polygonToCellsCount =
countNonNullIndexes(polygonToCellsOut, polygonToCellsSize);
int64_t childrenSize;
t_assertSuccess(
H3_EXPORT(cellToChildrenSize)(h, nextRes, &childrenSize));
H3Index *children = calloc(childrenSize, sizeof(H3Index));
t_assertSuccess(H3_EXPORT(cellToChildren)(h, nextRes, children));
int64_t cellToChildrenCount =
countNonNullIndexes(children, childrenSize);
t_assert(polygonToCellsCount == cellToChildrenCount,
"PolygonToCells count matches cellToChildren count");
for (int i = 0; i < childrenSize; i++) {
bool found = false;
if (children[i] == H3_NULL) continue;
for (int j = 0; j < polygonToCellsSize; j++) {
if (polygonToCellsOut[j] == children[i]) {
found = true;
break;
}
}
t_assert(
found,
"All indexes match between polygonToCells and cellToChildren");
}
free(polygonToCellsOut);
free(children);
}
}
SUITE(polygonToCells) {
sfGeoPolygon.geoloop = sfGeoLoop;
sfGeoPolygon.numHoles = 0;
holeGeoPolygon.geoloop = sfGeoLoop;
holeGeoPolygon.numHoles = 1;
holeGeoPolygon.holes = &holeGeoLoop;
emptyGeoPolygon.geoloop = emptyGeoLoop;
emptyGeoPolygon.numHoles = 0;
invalidGeoPolygon.geoloop = invalidGeoLoop;
invalidGeoPolygon.numHoles = 0;
invalid2GeoPolygon.geoloop = invalid2GeoLoop;
invalid2GeoPolygon.numHoles = 0;
pointGeoPolygon.geoloop = pointGeoLoop;
pointGeoPolygon.numHoles = 0;
lineGeoPolygon.geoloop = lineGeoLoop;
lineGeoPolygon.numHoles = 0;
// --------------------------------------------
// maxPolygonToCellsSize
// --------------------------------------------
TEST(maxPolygonToCellsSize) {
int64_t numHexagons;
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0,
&numHexagons));
t_assert(numHexagons == 5613, "got expected max polygonToCells size");
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&holeGeoPolygon, 9, 0,
&numHexagons));
t_assert(numHexagons == 5613,
"got expected max polygonToCells size (hole)");
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&emptyGeoPolygon, 9, 0,
&numHexagons));
t_assert(numHexagons == 15,
"got expected max polygonToCells size (empty)");
}
TEST(maxPolygonToCellsSizeInvalid) {
int64_t numHexagons;
t_assert(
H3_EXPORT(maxPolygonToCellsSize)(&invalidGeoPolygon, 9, 0,
&numHexagons) == E_FAILED,
"Cannot determine cell size to invalid geo polygon with Infinity");
t_assert(H3_EXPORT(maxPolygonToCellsSize)(&invalid2GeoPolygon, 9, 0,
&numHexagons) == E_FAILED,
"Cannot determine cell size to invalid geo polygon with NaNs");
}
TEST(maxPolygonToCellsSizePoint) {
int64_t numHexagons;
t_assert(H3_EXPORT(maxPolygonToCellsSize)(&pointGeoPolygon, 9, 0,
&numHexagons) == E_FAILED,
"Cannot estimate for single point");
}
TEST(maxPolygonToCellsSizeLine) {
int64_t numHexagons;
t_assert(H3_EXPORT(maxPolygonToCellsSize)(&lineGeoPolygon, 9, 0,
&numHexagons) == E_FAILED,
"Cannot estimate for straight line");
}
// --------------------------------------------
// polygonToCells
// --------------------------------------------
TEST(polygonToCells) {
int64_t numHexagons;
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0,
&numHexagons));
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(
H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, 0, hexagons));
int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons);
t_assert(actualNumIndexes == 1253, "got expected polygonToCells size");
free(hexagons);
}
TEST(polygonToCellsHole) {
int64_t numHexagons;
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&holeGeoPolygon, 9, 0,
&numHexagons));
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(
H3_EXPORT(polygonToCells)(&holeGeoPolygon, 9, 0, hexagons));
int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons);
t_assert(actualNumIndexes == 1214,
"got expected polygonToCells size (hole)");
free(hexagons);
}
TEST(polygonToCellsEmpty) {
int64_t numHexagons;
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&emptyGeoPolygon, 9, 0,
&numHexagons));
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(
H3_EXPORT(polygonToCells)(&emptyGeoPolygon, 9, 0, hexagons));
int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons);
t_assert(actualNumIndexes == 0,
"got expected polygonToCells size (empty)");
free(hexagons);
}
TEST(polygonToCellsExact) {
LatLng somewhere = {1, 2};
H3Index origin;
t_assertSuccess(H3_EXPORT(latLngToCell)(&somewhere, 9, &origin));
CellBoundary boundary;
H3_EXPORT(cellToBoundary)(origin, &boundary);
LatLng *verts = calloc(boundary.numVerts + 1, sizeof(LatLng));
for (int i = 0; i < boundary.numVerts; i++) {
verts[i] = boundary.verts[i];
}
verts[boundary.numVerts] = boundary.verts[0];
GeoLoop someGeoLoop;
someGeoLoop.numVerts = boundary.numVerts + 1;
someGeoLoop.verts = verts;
GeoPolygon someHexagon;
someHexagon.geoloop = someGeoLoop;
someHexagon.numHoles = 0;
int64_t numHexagons;
t_assertSuccess(
H3_EXPORT(maxPolygonToCellsSize)(&someHexagon, 9, 0, &numHexagons));
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(
H3_EXPORT(polygonToCells)(&someHexagon, 9, 0, hexagons));
int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons);
t_assert(actualNumIndexes == 1, "got expected polygonToCells size (1)");
free(hexagons);
free(verts);
}
TEST(polygonToCellsTransmeridian) {
LatLng primeMeridianVerts[] = {
{0.01, 0.01}, {0.01, -0.01}, {-0.01, -0.01}, {-0.01, 0.01}};
GeoLoop primeMeridianGeoLoop = {.numVerts = 4,
.verts = primeMeridianVerts};
GeoPolygon primeMeridianGeoPolygon = {.geoloop = primeMeridianGeoLoop,
.numHoles = 0};
LatLng transMeridianVerts[] = {{0.01, -M_PI + 0.01},
{0.01, M_PI - 0.01},
{-0.01, M_PI - 0.01},
{-0.01, -M_PI + 0.01}};
GeoLoop transMeridianGeoLoop = {.numVerts = 4,
.verts = transMeridianVerts};
GeoPolygon transMeridianGeoPolygon = {.geoloop = transMeridianGeoLoop,
.numHoles = 0};
LatLng transMeridianHoleVerts[] = {{0.005, -M_PI + 0.005},
{0.005, M_PI - 0.005},
{-0.005, M_PI - 0.005},
{-0.005, -M_PI + 0.005}};
GeoLoop transMeridianHoleGeoLoop = {.numVerts = 4,
.verts = transMeridianHoleVerts};
GeoPolygon transMeridianHoleGeoPolygon = {
.geoloop = transMeridianGeoLoop,
.numHoles = 1,
.holes = &transMeridianHoleGeoLoop};
GeoPolygon transMeridianFilledHoleGeoPolygon = {
.geoloop = transMeridianHoleGeoLoop, .numHoles = 0};
int64_t expectedSize;
// Prime meridian case
expectedSize = 4228;
int64_t numHexagons;
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(
&primeMeridianGeoPolygon, 7, 0, &numHexagons));
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(H3_EXPORT(polygonToCells)(&primeMeridianGeoPolygon, 7,
0, hexagons));
int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons);
t_assert(actualNumIndexes == expectedSize,
"got expected polygonToCells size (prime meridian)");
// Transmeridian case
// This doesn't exactly match the prime meridian count because of slight
// differences in hex size and grid offset between the two cases
expectedSize = 4238;
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(
&transMeridianGeoPolygon, 7, 0, &numHexagons));
H3Index *hexagonsTM = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(H3_EXPORT(polygonToCells)(&transMeridianGeoPolygon, 7,
0, hexagonsTM));
actualNumIndexes = countNonNullIndexes(hexagonsTM, numHexagons);
t_assert(actualNumIndexes == expectedSize,
"got expected polygonToCells size (transmeridian)");
// Transmeridian filled hole case -- only needed for calculating hole
// size
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(
&transMeridianFilledHoleGeoPolygon, 7, 0, &numHexagons));
H3Index *hexagonsTMFH = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(H3_EXPORT(polygonToCells)(
&transMeridianFilledHoleGeoPolygon, 7, 0, hexagonsTMFH));
int64_t actualNumHoleIndexes =
countNonNullIndexes(hexagonsTMFH, numHexagons);
// Transmeridian hole case
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(
&transMeridianHoleGeoPolygon, 7, 0, &numHexagons));
H3Index *hexagonsTMH = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(H3_EXPORT(polygonToCells)(&transMeridianHoleGeoPolygon,
7, 0, hexagonsTMH));
actualNumIndexes = countNonNullIndexes(hexagonsTMH, numHexagons);
t_assert(actualNumIndexes == expectedSize - actualNumHoleIndexes,
"got expected polygonToCells size (transmeridian hole)");
free(hexagons);
free(hexagonsTM);
free(hexagonsTMFH);
free(hexagonsTMH);
}
TEST(polygonToCellsTransmeridianComplex) {
// This polygon is "complex" in that it has > 4 vertices - this
// tests for a bug that was taking the max and min longitude as
// the bounds for transmeridian polygons
LatLng verts[] = {{0.1, -M_PI + 0.00001}, {0.1, M_PI - 0.00001},
{0.05, M_PI - 0.2}, {-0.1, M_PI - 0.00001},
{-0.1, -M_PI + 0.00001}, {-0.05, -M_PI + 0.2}};
GeoLoop geoloop = {.numVerts = 6, .verts = verts};
GeoPolygon polygon = {.geoloop = geoloop, .numHoles = 0};
int64_t numHexagons;
t_assertSuccess(
H3_EXPORT(maxPolygonToCellsSize)(&polygon, 4, 0, &numHexagons));
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(H3_EXPORT(polygonToCells)(&polygon, 4, 0, hexagons));
int64_t actualNumIndexes = countNonNullIndexes(hexagons, numHexagons);
t_assert(actualNumIndexes == 1204,
"got expected polygonToCells size (complex transmeridian)");
free(hexagons);
}
TEST(polygonToCellsPentagon) {
H3Index pentagon;
setH3Index(&pentagon, 9, 24, 0);
LatLng coord;
H3_EXPORT(cellToLatLng)(pentagon, &coord);
// Length of half an edge of the polygon, in radians
double edgeLength2 = H3_EXPORT(degsToRads)(0.001);
LatLng boundingTopRigt = coord;
boundingTopRigt.lat += edgeLength2;
boundingTopRigt.lng += edgeLength2;
LatLng boundingTopLeft = coord;
boundingTopLeft.lat += edgeLength2;
boundingTopLeft.lng -= edgeLength2;
LatLng boundingBottomRight = coord;
boundingBottomRight.lat -= edgeLength2;
boundingBottomRight.lng += edgeLength2;
LatLng boundingBottomLeft = coord;
boundingBottomLeft.lat -= edgeLength2;
boundingBottomLeft.lng -= edgeLength2;
LatLng verts[] = {boundingBottomLeft, boundingTopLeft, boundingTopRigt,
boundingBottomRight};
GeoLoop geoloop;
geoloop.verts = verts;
geoloop.numVerts = 4;
GeoPolygon polygon;
polygon.geoloop = geoloop;
polygon.numHoles = 0;
int64_t numHexagons;
t_assertSuccess(
H3_EXPORT(maxPolygonToCellsSize)(&polygon, 9, 0, &numHexagons));
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
t_assertSuccess(H3_EXPORT(polygonToCells)(&polygon, 9, 0, hexagons));
int found = 0;
int numPentagons = 0;
for (int i = 0; i < numHexagons; i++) {
if (hexagons[i] != 0) {
found++;
}
if (H3_EXPORT(isPentagon)(hexagons[i])) {
numPentagons++;
}
}
t_assert(found == 1, "one index found");
t_assert(numPentagons == 1, "one pentagon found");
free(hexagons);
}
TEST(invalidFlags) {
int64_t numHexagons;
for (uint32_t flags = CONTAINMENT_INVALID; flags <= 32; flags++) {
t_assert(
H3_EXPORT(maxPolygonToCellsSize)(
&sfGeoPolygon, 9, flags, &numHexagons) == E_OPTION_INVALID,
"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 = CONTAINMENT_INVALID; flags <= 32; flags++) {
t_assert(H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, flags,
hexagons) == E_OPTION_INVALID,
"Flags other than polyfill modes are invalid for "
"polygonToCells");
}
free(hexagons);
}
TEST(polygonToCellsInvalidPolygon) {
// Chosen arbitrarily, polygonToCells should error out before this is an
// issue.
int64_t numHexagons = 0;
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
t_assert(H3_EXPORT(polygonToCells)(&invalidGeoPolygon, 9, 0,
hexagons) == E_FAILED,
"Invalid geo polygon cannot be evaluated");
free(hexagons);
}
TEST(fillIndex) {
iterateAllIndexesAtRes(0, fillIndex_assertions);
iterateAllIndexesAtRes(1, fillIndex_assertions);
iterateAllIndexesAtRes(2, fillIndex_assertions);
}
TEST(getEdgeHexagonsInvalid) {
int64_t numHexagons = 100;
H3Index *search = calloc(numHexagons, sizeof(H3Index));
assert(search != NULL);
H3Index *found = calloc(numHexagons, sizeof(H3Index));
assert(found != NULL);
int res = 0;
int64_t numSearchHexes = 0;
H3Error err = _getEdgeHexagons(&invalidGeoLoop, numHexagons, res,
&numSearchHexes, search, found);
t_assert(err != E_SUCCESS,
"_getEdgeHexagons returns error for invalid geoloop");
free(found);
free(search);
}
}