Skip to content

Commit

Permalink
Alternate polygonToCells algorithm (#785)
Browse files Browse the repository at this point in the history
This PR implements an alternate algorithm for `polygonToCells`, which provides `polygonToCompactCells` as a byproduct.

### Algorithm Overview
```
let cell = base cell 0
while cell:
  res = getResolution(cell)
  if res == target res:
     if cell is in polygon:
        set output to cell and return
  if res < target res:
     let bbox = boundingBox(cell) // where bbox is padded to contain all descendants
     if bbox intersects polygon:
        if bbox is contained by polygon:
          set output to cell and return
        cell = first child of cell
        continue
  cell = next cell // where "next" is the next sibling, or next sibling of parent, etc
when cell is null we're done
```

### Results
 -  This PR includes an iterator-based algo with full test parity to the current algorithm. In its iterator form, it takes very little memory and that memory does not scale with the size of the output - so we can stream cells out of a very large polyfill process without worrying about memory issues.
- The new algo gives compact (or nearly compact) output by default, so we get `polygonToCellsCompact` effectively for free.
- The performance isn't exactly better or worse than the current algorithm - it just has different characteristics. In benchmarks, it's faster than the current algorithm in roughly 80% of cases
  • Loading branch information
nrabinowitz authored Oct 31, 2023
1 parent 5aeff3d commit 5d7c1c1
Show file tree
Hide file tree
Showing 25 changed files with 2,555 additions and 58 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The public API of this library consists of the functions declared in file

## [Unreleased]
### Changed
- Replace internal algorithm for `polygonToCells` with a new version that is more memory-efficient (#785)
- Reorganize tests into public / internal. (#762)
- Performance enhancement for aarch64, should not affect other platforms (#790, #792)

Expand Down
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ set(LIB_SOURCE_FILES
src/h3lib/include/bbox.h
src/h3lib/include/polygon.h
src/h3lib/include/polygonAlgos.h
src/h3lib/include/polyfill.h
src/h3lib/include/h3Index.h
src/h3lib/include/directedEdge.h
src/h3lib/include/latLng.h
Expand All @@ -146,6 +147,7 @@ set(LIB_SOURCE_FILES
src/h3lib/lib/coordijk.c
src/h3lib/lib/bbox.c
src/h3lib/lib/polygon.c
src/h3lib/lib/polyfill.c
src/h3lib/lib/h3Index.c
src/h3lib/lib/vec2d.c
src/h3lib/lib/vec3d.c
Expand Down Expand Up @@ -192,7 +194,9 @@ set(OTHER_SOURCE_FILES
src/apps/testapps/testVertexGraphInternal.c
src/apps/testapps/testCompactCells.c
src/apps/testapps/testPolygonToCells.c
src/apps/testapps/testPolygonToCellsExperimental.c
src/apps/testapps/testPolygonToCellsReported.c
src/apps/testapps/testPolygonToCellsReportedExperimental.c
src/apps/testapps/testPentagonIndexes.c
src/apps/testapps/testGridDisk.c
src/apps/testapps/testGridDiskInternal.c
Expand All @@ -209,6 +213,7 @@ set(OTHER_SOURCE_FILES
src/apps/testapps/testCellToLatLng.c
src/apps/testapps/testCellToCenterChild.c
src/apps/testapps/testCellToChildren.c
src/apps/testapps/testCellToBBoxExhaustive.c
src/apps/testapps/testCellToChildPos.c
src/apps/testapps/testGetIcosahedronFaces.c
src/apps/testapps/testLatLng.c
Expand All @@ -220,6 +225,7 @@ set(OTHER_SOURCE_FILES
src/apps/testapps/testVertexInternal.c
src/apps/testapps/testVertexExhaustive.c
src/apps/testapps/testPolygonInternal.c
src/apps/testapps/testPolyfillInternal.c
src/apps/testapps/testVec2dInternal.c
src/apps/testapps/testVec3dInternal.c
src/apps/testapps/testDirectedEdge.c
Expand Down Expand Up @@ -270,6 +276,7 @@ set(OTHER_SOURCE_FILES
src/apps/fuzzers/fuzzerInternalAlgos.c
src/apps/fuzzers/fuzzerInternalCoordIjk.c
src/apps/benchmarks/benchmarkPolygonToCells.c
src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c
src/apps/benchmarks/benchmarkPolygon.c
src/apps/benchmarks/benchmarkCellsToLinkedMultiPolygon.c
src/apps/benchmarks/benchmarkCellToChildren.c
Expand Down Expand Up @@ -566,6 +573,7 @@ if(BUILD_BENCHMARKS)
add_h3_benchmark(benchmarkCellsToLinkedMultiPolygon src/apps/benchmarks/benchmarkCellsToLinkedMultiPolygon.c)
add_h3_benchmark(benchmarkCellToChildren src/apps/benchmarks/benchmarkCellToChildren.c)
add_h3_benchmark(benchmarkPolygonToCells src/apps/benchmarks/benchmarkPolygonToCells.c)
add_h3_benchmark(benchmarkPolygonToCellsExperimental src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c)
if(ENABLE_REQUIRES_ALL_SYMBOLS)
add_h3_benchmark(benchmarkPolygon src/apps/benchmarks/benchmarkPolygon.c)
endif()
Expand Down
4 changes: 4 additions & 0 deletions CMakeTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ add_h3_test(testCellsToLinkedMultiPolygon src/apps/testapps/testCellsToLinkedMul
add_h3_test(testH3SetToVertexGraphInternal src/apps/testapps/testH3SetToVertexGraphInternal.c)
add_h3_test(testLinkedGeoInternal src/apps/testapps/testLinkedGeoInternal.c)
add_h3_test(testPolygonToCells src/apps/testapps/testPolygonToCells.c)
add_h3_test(testPolygonToCellsExperimental src/apps/testapps/testPolygonToCellsExperimental.c)
add_h3_test(testPolygonToCellsReported src/apps/testapps/testPolygonToCellsReported.c)
add_h3_test(testPolygonToCellsReportedExperimental src/apps/testapps/testPolygonToCellsReportedExperimental.c)
add_h3_test(testVertexGraphInternal src/apps/testapps/testVertexGraphInternal.c)
add_h3_test(testDirectedEdge src/apps/testapps/testDirectedEdge.c)
add_h3_test(testLatLng src/apps/testapps/testLatLng.c)
Expand All @@ -195,6 +197,7 @@ add_h3_test(testBBoxInternal src/apps/testapps/testBBoxInternal.c)
add_h3_test(testVertex src/apps/testapps/testVertex.c)
add_h3_test(testVertexInternal src/apps/testapps/testVertexInternal.c)
add_h3_test(testPolygonInternal src/apps/testapps/testPolygonInternal.c)
add_h3_test(testPolyfillInternal src/apps/testapps/testPolyfillInternal.c)
add_h3_test(testVec2dInternal src/apps/testapps/testVec2dInternal.c)
add_h3_test(testVec3dInternal src/apps/testapps/testVec3dInternal.c)
add_h3_test(testCellToLocalIj src/apps/testapps/testCellToLocalIj.c)
Expand Down Expand Up @@ -223,6 +226,7 @@ add_h3_test(testCellToLocalIjExhaustive src/apps/testapps/testCellToLocalIjExhau
add_h3_test(testGridPathCellsExhaustive src/apps/testapps/testGridPathCellsExhaustive.c)
add_h3_test(testGridDistanceExhaustive src/apps/testapps/testGridDistanceExhaustive.c)
add_h3_test(testH3CellAreaExhaustive src/apps/testapps/testH3CellAreaExhaustive.c)
add_h3_test(testCellToBBoxExhaustive src/apps/testapps/testCellToBBoxExhaustive.c)

add_h3_cli_test(testCliCellToLatLng "cellToLatLng -c 8928342e20fffff" "37.5012466151, -122.5003039349")
add_h3_cli_test(testCliLatLngToCell "latLngToCell --lat 20 --lng 123 -r 2" "824b9ffffffffff")
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ You can run a faster test suite that excludes the most expensive tests with `mak

#### Coverage

You can generate a code coverage report if `lcov` is installed, and if the project was built with the `CMAKE_BUILD_TYPE=Debug` option.
You can generate a code coverage report if `lcov` is installed, and if the project was built with the `CMAKE_BUILD_TYPE=Debug` and `ENABLE_COVERAGE=ON` options.
For example, from a clean repository, you could run:

```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON ..
make
make coverage
```
Expand Down
3 changes: 3 additions & 0 deletions src/apps/applib/include/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <stdio.h>

#include "bbox.h"
#include "coordijk.h"
#include "h3api.h"

Expand All @@ -46,6 +47,8 @@ void geoPrintNoFmt(const LatLng *p);
void geoPrintlnNoFmt(const LatLng *p);
void cellBoundaryPrint(const CellBoundary *b);
void cellBoundaryPrintln(const CellBoundary *b);
void bboxPrint(const BBox *bbox);
void bboxPrintln(const BBox *bbox);

void randomGeo(LatLng *p);

Expand Down
12 changes: 12 additions & 0 deletions src/apps/applib/lib/utility.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ void cellBoundaryPrintln(const CellBoundary *b) {
printf("}\n");
}

void bboxPrint(const BBox *bbox) {
printf(
"bbox {%.9lf, %.9lf, %.9lf, %.9lf}", H3_EXPORT(radsToDegs)(bbox->north),
H3_EXPORT(radsToDegs)(bbox->south), H3_EXPORT(radsToDegs)(bbox->east),
H3_EXPORT(radsToDegs)(bbox->west));
}

void bboxPrintln(const BBox *bbox) {
bboxPrint(bbox);
printf("\n");
}

/**
* Apply callback for every unidirectional edge at the given resolution.
*/
Expand Down
146 changes: 146 additions & 0 deletions src/apps/benchmarks/benchmarkPolygonToCellsExperimental.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright 2017, 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 "algos.h"
#include "benchmark.h"
#include "h3api.h"
#include "polyfill.h"

// Fixtures
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}};
GeoLoop sfGeoLoop;
GeoPolygon sfGeoPolygon;

LatLng alamedaVerts[] = {{0.6597959342671712, -2.133241848488897},
{0.6597959348850178, -2.133241848495878},
{0.6598352639563587, -2.1331688423977755},
{0.6601346536539207, -2.13270417124178},
{0.6601594763880223, -2.1326680320633344},
{0.6601512007732382, -2.1326594176574534},
{0.6598535076212304, -2.1323049630593562},
{0.6596565748646488, -2.132069889917591},
{0.6594645035394391, -2.131843148468039},
{0.6593438094209757, -2.1316994860539844},
{0.6591174422311021, -2.131429776816562},
{0.658849344286881, -2.1311111485483867},
{0.6588348862079956, -2.1310988536794455},
{0.6586273138317915, -2.131668420800747},
{0.6583729538174264, -2.132370426573979},
{0.6582479206289285, -2.132718691911663},
{0.6582322393220743, -2.1327614200082317},
{0.6583003647098981, -2.132837478687196},
{0.6584457274847966, -2.132827956758973},
{0.6585526679060995, -2.1330231566043203},
{0.6587379099516777, -2.1331602726234538},
{0.6587273684736642, -2.1332676321559063},
{0.6584638025857692, -2.133305719954319},
{0.6583545950288919, -2.1334323622944993},
{0.6584427148370682, -2.1335885223323947},
{0.6584715236640714, -2.133649780409862},
{0.6584715242505019, -2.133649780481421},
{0.658474662092443, -2.1336459234695804},
{0.6591666596433436, -2.1348354004882926},
{0.6591809355063646, -2.1348424115474565},
{0.6593477498700266, -2.1351460576998926},
{0.6597155087395117, -2.1351049454274},
{0.6597337410387994, -2.135113899444683},
{0.6598277083823935, -2.1351065432309517},
{0.659837290351688, -2.1350919904836627},
{0.6598391300107502, -2.1350911731005957},
{0.6598335712627461, -2.1350732321630828},
{0.6597162034032434, -2.134664026354221},
{0.6596785831942451, -2.134651647657116},
{0.6596627824684727, -2.13458880305965},
{0.6596785832500957, -2.134530719130462},
{0.6596093592822273, -2.13428052987356},
{0.6596116166352313, -2.134221493755564},
{0.6595973199434513, -2.134146270344056},
{0.6595536764042369, -2.1340805688066653},
{0.6594611172376618, -2.133753252031165},
{0.6594829406269346, -2.1337342082305697},
{0.6594897134102581, -2.1337104032834757},
{0.6597920983773051, -2.1332343063312775},
{0.6597959342671712, -2.133241848488897}};
GeoLoop alamedaGeoLoop;
GeoPolygon alamedaGeoPolygon;

LatLng southernVerts[] = {{0.6367481147484843, -2.1290865397798906},
{0.6367481152301953, -2.129086539469222},
{0.6367550754426818, -2.128887436716856},
{0.6367816002113981, -2.1273204058681094},
{0.6380814125349741, -2.127201274803692},
{0.6388614350074809, -2.12552061082428},
{0.6393520289210095, -2.124274316938293},
{0.639524834205869, -2.122168447308359},
{0.6405714857447717, -2.122083222593005},
{0.640769478635285, -2.120979885974894},
{0.6418936996869471, -2.1147667448862255},
{0.6419094141707652, -2.1146521242709584},
{0.6269997808948107, -2.1038647304637257},
{0.6252080524974937, -2.1195521728170457},
{0.626379700264057, -2.1203708632511162},
{0.6282200029232767, -2.1210412050690723},
{0.6283657301211779, -2.1219496416754393},
{0.6305651783819565, -2.123628532238016},
{0.6308259852882764, -2.124225549648211},
{0.6317049665784865, -2.124887756638367},
{0.6323403882676475, -2.1266205835454053},
{0.6334397909415498, -2.1277211741619553},
{0.6367481147484843, -2.1290865397798906}};
GeoLoop southernGeoLoop;
GeoPolygon southernGeoPolygon;

BEGIN_BENCHMARKS();

sfGeoLoop.numVerts = 6;
sfGeoLoop.verts = sfVerts;
sfGeoPolygon.geoloop = sfGeoLoop;

alamedaGeoLoop.numVerts = 50;
alamedaGeoLoop.verts = alamedaVerts;
alamedaGeoPolygon.geoloop = alamedaGeoLoop;

southernGeoLoop.numVerts = 23;
southernGeoLoop.verts = southernVerts;
southernGeoPolygon.geoloop = southernGeoLoop;

int64_t numHexagons;
H3Index *hexagons;

BENCHMARK(polygonToCellsSF, 500, {
H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0, &numHexagons);
hexagons = calloc(numHexagons, sizeof(H3Index));
H3_EXPORT(polygonToCellsExperimental)(&sfGeoPolygon, 9, 0, hexagons);
free(hexagons);
});

BENCHMARK(polygonToCellsAlameda, 500, {
H3_EXPORT(maxPolygonToCellsSize)(&alamedaGeoPolygon, 9, 0, &numHexagons);
hexagons = calloc(numHexagons, sizeof(H3Index));
H3_EXPORT(polygonToCellsExperimental)(&alamedaGeoPolygon, 9, 0, hexagons);
free(hexagons);
});

BENCHMARK(polygonToCellsSouthernExpansion, 10, {
H3_EXPORT(maxPolygonToCellsSize)(&southernGeoPolygon, 9, 0, &numHexagons);
hexagons = calloc(numHexagons, sizeof(H3Index));
H3_EXPORT(polygonToCellsExperimental)(&southernGeoPolygon, 9, 0, hexagons);
free(hexagons);
});

END_BENCHMARKS();
Loading

0 comments on commit 5d7c1c1

Please sign in to comment.