Skip to content

Commit 28b7107

Browse files
author
Isaac Brodsky
authored
polygonToCells API accepts a flags argument (#570)
* polygonToCells API accepts a flags argument * Fix build * Fix build * Fix test
1 parent adde6da commit 28b7107

File tree

9 files changed

+215
-73
lines changed

9 files changed

+215
-73
lines changed

Diff for: dev-docs/RFCs/v4.0.0/polyfill-modes-rfc.md

+111-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# RFC: Polyfill modes
22

3-
* **Authors**: -
4-
* **Date**: -
3+
* **Authors**: Isaac Brodsky (@isaacbrodsky)
4+
* **Date**: September 17, 2021
55
* **Status**: Draft
66

77
## Abstract
@@ -12,10 +12,118 @@ Our current polyfill algorithm allocates cells to polygons based on whether the
1212

1313
*Why is this important?*
1414

15+
* Inclusion test
16+
17+
A use case we want to support is testing whether a point could possibly be in a polygon by set containment. In Java this would look like:
18+
19+
```java
20+
final int R = ...;
21+
// Indexed as res R
22+
Set<String> cellsCoveringPolygon = ...;
23+
24+
public boolean polygonContainsPoint(double latitude, double longitude) {
25+
String h3Index = h3.latLngToCell(latitude, longitude, R);
26+
if (cellsCoveringPolygon.contains(h3Index)) {
27+
// The polygon possibly contains the point, so use a more expensive point
28+
// in polygon algorithm to determine.
29+
return polygonContainsPointExpensive(latitude, longitude);
30+
}
31+
// The polygon definitely does not contain the point, the expensive check
32+
// is not needed.
33+
return false;
34+
}
35+
```
36+
37+
* Analytics
38+
39+
There is no convenient function to index a polygon into the H3 grid for analytics purposes. Polygons that are small enough or shaped in specific ways will not be converted into cells, requiring workarounds in order to use them.
40+
41+
* Spherical geometry consistency
42+
43+
Most of the H3 library uses spherical geoemtry. For example, cell boundaries are spherical hexagons
44+
and pentagons. The `polyfill` function is different that it assumes Cartesian geometry. For
45+
consistency with the rest of the library, the `polyfill` functions should be able to use the same
46+
cell boundaries.
47+
48+
Maintaining a Cartesian option is useful for cases where polygons have been drawn on a projected map
49+
and the boundaries should be the same.
50+
51+
* Very large polygons
52+
53+
Polyfills of very large polygons require allocating large blocks of memory, and spending large
54+
amounts of time in a library function without progress information being available to the caller.
55+
(To be determined if this is in scope for this RFC or for another.)
56+
1557
## Approaches
1658

1759
*What are the various options to address this issue?*
1860

61+
On an API level, we will need to expose the containment mode and spherical/Cartesian choice as
62+
options to the caller.
63+
64+
Internally, we would like to reuse the same implementation as much as possible, but change the
65+
exact inclusion check used for each mode.
66+
67+
### Comparisons
68+
69+
#### S2
70+
71+
Contains separate functions for [intersection and containment](http://s2geometry.io/devguide/basic_types#s2polygon)
72+
73+
#### Turf library
74+
75+
Contains separate functions for [intersection](http://turfjs.org/docs/#booleanIntersects), [containment](http://turfjs.org/docs/#booleanContains), etc.
76+
77+
#### PostGIS
78+
79+
Contains separate functions for [intersection](https://postgis.net/docs/ST_Intersects.html), [containment](https://postgis.net/docs/ST_Contains.html), etc.
80+
81+
See also [Simple Feature Access - SQL](https://www.ogc.org/standards/sfs).
82+
83+
#### JTS
84+
85+
Separate predicatess. (Reference: [JTS Developer Guide](https://github.com/locationtech/jts/blob/master/doc/JTS%20Developer%20Guide.pdf))
86+
87+
#### GeoPandas
88+
89+
Contains separate functions for [intersection](https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoSeries.intersection.html), [containment](https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoSeries.contains.html), etc.
90+
1991
## Proposal
2092

21-
*What is the recommended approach?*
93+
*What is the recommended approach?*
94+
95+
The signature for `polygonToCells` and `maxPolygonToCellsSize` would be changed as follows:
96+
97+
```
98+
/** @brief maximum number of hexagons that could be in the geoloop */
99+
DECLSPEC H3Error H3_EXPORT(maxPolygonToCellsSize)(const GeoPolygon *geoPolygon,
100+
int res, uint32_t flags, int64_t *out);
101+
102+
/** @brief hexagons within the given geopolygon */
103+
DECLSPEC H3Error H3_EXPORT(polygonToCells)(const GeoPolygon *geoPolygon,
104+
int res, uint32_t flags, H3Index *out);
105+
```
106+
107+
`flags` would have the following possible bit layout:
108+
109+
| Bits | Meaning
110+
| ---------- | -------
111+
| 1-2 (LSB) | If 0, containment mode centroid.<br>If 1, containment mode cover.<br>If 2, containment mode intersects.<br>3 is a reserved value.
112+
| 3 | If 0, spherical containment.<br>If 1, cartesian containment (same as H3 version 3).
113+
| All others | Reserved and must be set to 0.
114+
115+
The same value used for `maxPolygonToCellsSize` must be used for the subsequent call to `polygonToCells`, just as the `GeoPolygon` and `res` must be the same.
116+
117+
In bindings, this could be expressed using enums, for example:
118+
119+
```python
120+
polygon_to_cells(polygon, res=res, cartesian=True, containment=h3.Containment.CENTROID)
121+
```
122+
123+
```js
124+
polygonToCells(polygon, {res, cartesian: true, containment: h3.Containment.CENTROID})
125+
```
126+
127+
```java
128+
polygon(polygon).cartesian(true).containment(h3.Containment.CENTROID).toCells(res)
129+
```

Diff for: src/apps/benchmarks/benchmarkPolygonToCells.c

+6-6
Original file line numberDiff line numberDiff line change
@@ -122,23 +122,23 @@ int64_t numHexagons;
122122
H3Index *hexagons;
123123

124124
BENCHMARK(polygonToCellsSF, 500, {
125-
H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, &numHexagons);
125+
H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0, &numHexagons);
126126
hexagons = calloc(numHexagons, sizeof(H3Index));
127-
H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, hexagons);
127+
H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, 0, hexagons);
128128
free(hexagons);
129129
});
130130

131131
BENCHMARK(polygonToCellsAlameda, 500, {
132-
H3_EXPORT(maxPolygonToCellsSize)(&alamedaGeoPolygon, 9, &numHexagons);
132+
H3_EXPORT(maxPolygonToCellsSize)(&alamedaGeoPolygon, 9, 0, &numHexagons);
133133
hexagons = calloc(numHexagons, sizeof(H3Index));
134-
H3_EXPORT(polygonToCells)(&alamedaGeoPolygon, 9, hexagons);
134+
H3_EXPORT(polygonToCells)(&alamedaGeoPolygon, 9, 0, hexagons);
135135
free(hexagons);
136136
});
137137

138138
BENCHMARK(polygonToCellsSouthernExpansion, 10, {
139-
H3_EXPORT(maxPolygonToCellsSize)(&southernGeoPolygon, 9, &numHexagons);
139+
H3_EXPORT(maxPolygonToCellsSize)(&southernGeoPolygon, 9, 0, &numHexagons);
140140
hexagons = calloc(numHexagons, sizeof(H3Index));
141-
H3_EXPORT(polygonToCells)(&southernGeoPolygon, 9, hexagons);
141+
H3_EXPORT(polygonToCells)(&southernGeoPolygon, 9, 0, hexagons);
142142
free(hexagons);
143143
});
144144

Diff for: src/apps/fuzzers/fuzzerPolygonToCells.c

+6-5
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ int populateGeoLoop(GeoLoop *g, const uint8_t *data, size_t *offset,
5050
return 0;
5151
}
5252

53-
void run(GeoPolygon *geoPolygon, int res) {
53+
void run(GeoPolygon *geoPolygon, uint32_t flags, int res) {
5454
int64_t sz;
55-
H3Error err = H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, &sz);
55+
H3Error err = H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &sz);
5656
if (!err && sz < MAX_SZ) {
5757
H3Index *out = calloc(sz, sizeof(H3Index));
58-
H3_EXPORT(polygonToCells)(geoPolygon, res, out);
58+
H3_EXPORT(polygonToCells)(geoPolygon, res, flags, out);
5959
free(out);
6060
}
6161
}
@@ -88,9 +88,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
8888
}
8989
}
9090

91-
run(&geoPolygon, res);
91+
// TODO: Fuzz the `flags` input as well when it has meaningful input
92+
run(&geoPolygon, 0, res);
9293
geoPolygon.numHoles = 0;
93-
run(&geoPolygon, res);
94+
run(&geoPolygon, 0, res);
9495
free(geoPolygon.holes);
9596

9697
return 0;

Diff for: src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c

+5-4
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
const int MAX_RES = 15;
2525
const int MAX_SZ = 4000000;
2626

27-
void run(GeoPolygon *geoPolygon, int res) {
27+
void run(GeoPolygon *geoPolygon, uint32_t flags, int res) {
2828
int64_t sz;
29-
H3Error err = H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, &sz);
29+
H3Error err = H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &sz);
3030
if (!err && sz < MAX_SZ) {
3131
H3Index *out = calloc(sz, sizeof(H3Index));
32-
H3_EXPORT(polygonToCells)(geoPolygon, res, out);
32+
H3_EXPORT(polygonToCells)(geoPolygon, res, flags, out);
3333
free(out);
3434
}
3535
}
@@ -50,7 +50,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
5050
// Offset by 1 since *data was used for `res`, above.
5151
geoPolygon.geoloop.verts = (LatLng *)(data + 1);
5252

53-
run(&geoPolygon, res);
53+
// TODO: Fuzz the `flags` input as well when it has meaningful input
54+
run(&geoPolygon, 0, res);
5455

5556
return 0;
5657
}

Diff for: src/apps/testapps/testH3Memory.c

+6-6
Original file line numberDiff line numberDiff line change
@@ -189,31 +189,31 @@ SUITE(h3Memory) {
189189
sfGeoPolygon.numHoles = 0;
190190

191191
int64_t numHexagons;
192-
t_assertSuccess(
193-
H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, &numHexagons));
192+
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSize)(&sfGeoPolygon, 9, 0,
193+
&numHexagons));
194194
H3Index *hexagons = calloc(numHexagons, sizeof(H3Index));
195195

196196
resetMemoryCounters(0);
197197
failAlloc = true;
198-
H3Error err = H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, hexagons);
198+
H3Error err = H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, 0, hexagons);
199199
t_assert(err == E_MEMORY, "polygonToCells failed (1)");
200200
t_assert(actualAllocCalls == 1, "alloc called once");
201201
t_assert(actualFreeCalls == 0, "free not called");
202202

203203
resetMemoryCounters(1);
204-
err = H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, hexagons);
204+
err = H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, 0, hexagons);
205205
t_assert(err == E_MEMORY, "polygonToCells failed (2)");
206206
t_assert(actualAllocCalls == 2, "alloc called twice");
207207
t_assert(actualFreeCalls == 1, "free called once");
208208

209209
resetMemoryCounters(2);
210-
err = H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, hexagons);
210+
err = H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, 0, hexagons);
211211
t_assert(err == E_MEMORY, "polygonToCells failed (3)");
212212
t_assert(actualAllocCalls == 3, "alloc called three times");
213213
t_assert(actualFreeCalls == 2, "free called twice");
214214

215215
resetMemoryCounters(3);
216-
err = H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, hexagons);
216+
err = H3_EXPORT(polygonToCells)(&sfGeoPolygon, 9, 0, hexagons);
217217
t_assert(err == E_SUCCESS, "polygonToCells succeeded (4)");
218218
t_assert(actualAllocCalls == 3, "alloc called three times");
219219
t_assert(actualFreeCalls == 3, "free called three times");

0 commit comments

Comments
 (0)