h3 3.6.0 → 3.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/ext/h3/src/CHANGELOG.md +7 -0
- data/ext/h3/src/CMakeLists.txt +4 -2
- data/ext/h3/src/README.md +1 -1
- data/ext/h3/src/VERSION +1 -1
- data/ext/h3/src/docs/community/bindings.md +4 -0
- data/ext/h3/src/src/apps/testapps/testCompact.c +34 -2
- data/ext/h3/src/src/apps/testapps/testGeoCoord.c +13 -1
- data/ext/h3/src/src/apps/testapps/testH3ToLocalIj.c +4 -2
- data/ext/h3/src/src/apps/testapps/testPolyfill.c +3 -3
- data/ext/h3/src/src/apps/testapps/testPolyfill_GH136.c +58 -0
- data/ext/h3/src/src/apps/testapps/testPolygon.c +26 -1
- data/ext/h3/src/src/h3lib/include/algos.h +8 -0
- data/ext/h3/src/src/h3lib/include/bbox.h +3 -1
- data/ext/h3/src/src/h3lib/lib/algos.c +240 -38
- data/ext/h3/src/src/h3lib/lib/bbox.c +50 -25
- data/ext/h3/src/src/h3lib/lib/h3Index.c +3 -0
- data/lib/h3/version.rb +1 -1
- data/spec/region_spec.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32f9595b2e8746f59aaef1c082fc3527a7eb3d7594d7eac1a59cb5875df6af3d
|
4
|
+
data.tar.gz: 8fd9c4ed2317a0211b4d3fa6360aede580bff5db691605b7fdd33b299720ba3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e0a51a2bdcf92096406af99d3823359768b99291f96be9ac6d113363c09efb71513cf09944eca1155549b63bbab4c628e2a6f89f8d77817df4029155375072e
|
7
|
+
data.tar.gz: 1c672e3d6d2262dee4bae5bf65587a870c9053b1815ec7ee34798fae228aed5bd338d57883ebb594f81b6c959006af86c460ddca473e4db401631e2400a11dcd
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
h3 (3.6.
|
4
|
+
h3 (3.6.1)
|
5
5
|
ffi (~> 1.9)
|
6
6
|
rgeo-geojson (~> 2.1)
|
7
7
|
zeitwerk (~> 2.1)
|
@@ -17,10 +17,10 @@ GEM
|
|
17
17
|
tins (~> 1.6)
|
18
18
|
diff-lcs (1.3)
|
19
19
|
docile (1.3.1)
|
20
|
-
ffi (1.11.
|
20
|
+
ffi (1.11.2)
|
21
21
|
json (2.1.0)
|
22
22
|
rake (12.3.2)
|
23
|
-
rgeo (2.1.
|
23
|
+
rgeo (2.1.1)
|
24
24
|
rgeo-geojson (2.1.1)
|
25
25
|
rgeo (>= 1.0.0)
|
26
26
|
rspec (3.8.0)
|
@@ -46,7 +46,7 @@ GEM
|
|
46
46
|
thor (0.19.4)
|
47
47
|
tins (1.20.2)
|
48
48
|
yard (0.9.20)
|
49
|
-
zeitwerk (2.1
|
49
|
+
zeitwerk (2.2.1)
|
50
50
|
|
51
51
|
PLATFORMS
|
52
52
|
ruby
|
data/ext/h3/src/CHANGELOG.md
CHANGED
@@ -7,6 +7,13 @@ The public API of this library consists of the functions declared in file
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [3.6.1] - 2019-11-11
|
11
|
+
### Fixed
|
12
|
+
- `compact` handles zero length input correctly. (#278)
|
13
|
+
- `bboxHexRadius` scaling factor adjusted to guarantee containment for `polyfill`. (#279)
|
14
|
+
- `polyfill` new algorithm for up to 3x perf boost. (#282)
|
15
|
+
- Fix CMake targets for KML generation. (#285)
|
16
|
+
|
10
17
|
## [3.6.0] - 2019-08-12
|
11
18
|
### Added
|
12
19
|
- `h3ToCenterChild` function to find center child at given resolution (#267)
|
data/ext/h3/src/CMakeLists.txt
CHANGED
@@ -143,6 +143,7 @@ set(OTHER_SOURCE_FILES
|
|
143
143
|
src/apps/testapps/testVertexGraph.c
|
144
144
|
src/apps/testapps/testCompact.c
|
145
145
|
src/apps/testapps/testPolyfill.c
|
146
|
+
src/apps/testapps/testPolyfill_GH136.c
|
146
147
|
src/apps/testapps/testPentagonIndexes.c
|
147
148
|
src/apps/testapps/testKRing.c
|
148
149
|
src/apps/testapps/testH3ToGeoBoundary.c
|
@@ -349,11 +350,11 @@ if(BUILD_FILTERS)
|
|
349
350
|
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "KML/res${resolution}cells.kml")
|
350
351
|
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "KML/res${resolution}centers.kml")
|
351
352
|
add_custom_target(kml_cells_${resolution}
|
352
|
-
COMMAND ${SHELL} "$<TARGET_FILE:h3ToHier> ${resolution} | $<TARGET_FILE:h3ToGeoBoundary>
|
353
|
+
COMMAND ${SHELL} "$<TARGET_FILE:h3ToHier> -r ${resolution} | $<TARGET_FILE:h3ToGeoBoundary> --kml --kml-name res${resolution}cells.kml --kml-description \"Res ${resolution} Cells\" > KML/res${resolution}cells.kml"
|
353
354
|
VERBATIM
|
354
355
|
DEPENDS create-kml-dir)
|
355
356
|
add_custom_target(kml_centers_${resolution}
|
356
|
-
COMMAND ${SHELL} "$<TARGET_FILE:h3ToHier> ${resolution} | $<TARGET_FILE:h3ToGeo>
|
357
|
+
COMMAND ${SHELL} "$<TARGET_FILE:h3ToHier> -r ${resolution} | $<TARGET_FILE:h3ToGeo> --kml --kml-name res${resolution}centers.kml --kml-description \"Res ${resolution} Centers\" > KML/res${resolution}centers.kml"
|
357
358
|
VERBATIM
|
358
359
|
DEPENDS create-kml-dir)
|
359
360
|
add_dependencies(kml
|
@@ -506,6 +507,7 @@ if(BUILD_TESTING)
|
|
506
507
|
add_h3_test(testH3SetToVertexGraph src/apps/testapps/testH3SetToVertexGraph.c)
|
507
508
|
add_h3_test(testLinkedGeo src/apps/testapps/testLinkedGeo.c)
|
508
509
|
add_h3_test(testPolyfill src/apps/testapps/testPolyfill.c)
|
510
|
+
add_h3_test(testPolyfill_GH136 src/apps/testapps/testPolyfill_GH136.c)
|
509
511
|
add_h3_test(testVertexGraph src/apps/testapps/testVertexGraph.c)
|
510
512
|
add_h3_test(testH3UniEdge src/apps/testapps/testH3UniEdge.c)
|
511
513
|
add_h3_test(testGeoCoord src/apps/testapps/testGeoCoord.c)
|
data/ext/h3/src/README.md
CHANGED
data/ext/h3/src/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.6.
|
1
|
+
3.6.1
|
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
As a C library, bindings can be made to call H3 functions from different programming languages. This page lists different bindings currently available. Contributions to this list are welcome, please feel free to open a [pull request](https://github.com/uber/h3/tree/master/docs/community/bindings.md).
|
4
4
|
|
5
|
+
## BigQuery
|
6
|
+
|
7
|
+
- [cartodb/bigquery-jslibs](https://github.com/CartoDB/bigquery-jslibs)
|
8
|
+
|
5
9
|
## C#
|
6
10
|
|
7
11
|
- [entrepreneur-interet-general/h3.standard](https://github.com/entrepreneur-interet-general/H3.Standard)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
* Copyright 2017-
|
2
|
+
* Copyright 2017-2019 Uber Technologies, Inc.
|
3
3
|
*
|
4
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
* you may not use this file except in compliance with the License.
|
@@ -152,6 +152,31 @@ SUITE(compact) {
|
|
152
152
|
"compact fails on duplicate input");
|
153
153
|
}
|
154
154
|
|
155
|
+
TEST(compact_empty) {
|
156
|
+
t_assert(H3_EXPORT(compact)(NULL, NULL, 0) == 0,
|
157
|
+
"compact succeeds on empty input");
|
158
|
+
}
|
159
|
+
|
160
|
+
TEST(compact_disparate) {
|
161
|
+
// Exercises a case where compaction needs to be tested but none is
|
162
|
+
// possible
|
163
|
+
const int numHex = 7;
|
164
|
+
H3Index disparate[] = {0, 0, 0, 0, 0, 0, 0};
|
165
|
+
for (int i = 0; i < numHex; i++) {
|
166
|
+
setH3Index(&disparate[i], 1, i, CENTER_DIGIT);
|
167
|
+
}
|
168
|
+
H3Index output[] = {0, 0, 0, 0, 0, 0, 0};
|
169
|
+
|
170
|
+
t_assert(H3_EXPORT(compact)(disparate, output, numHex) == 0,
|
171
|
+
"compact succeeds on disparate input");
|
172
|
+
|
173
|
+
// Assumes that `output` is an exact copy of `disparate`, including
|
174
|
+
// the ordering (which may not necessarily be the case)
|
175
|
+
for (int i = 0; i < numHex; i++) {
|
176
|
+
t_assert(disparate[i] == output[i], "output set equals input set");
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
155
180
|
TEST(uncompact_wrongRes) {
|
156
181
|
int numHex = 3;
|
157
182
|
H3Index someHexagons[] = {0, 0, 0};
|
@@ -221,6 +246,13 @@ SUITE(compact) {
|
|
221
246
|
free(result);
|
222
247
|
}
|
223
248
|
|
249
|
+
TEST(uncompact_empty) {
|
250
|
+
int uncompactSz = H3_EXPORT(maxUncompactSize)(NULL, 0, 0);
|
251
|
+
t_assert(uncompactSz == 0, "maxUncompactSize accepts empty input");
|
252
|
+
t_assert(H3_EXPORT(uncompact)(NULL, 0, NULL, 0, 0) == 0,
|
253
|
+
"uncompact accepts empty input");
|
254
|
+
}
|
255
|
+
|
224
256
|
TEST(uncompact_onlyZero) {
|
225
257
|
// maxUncompactSize and uncompact both permit 0 indexes
|
226
258
|
// in the input array, and skip them. When only a zero is
|
@@ -237,7 +269,7 @@ SUITE(compact) {
|
|
237
269
|
free(children);
|
238
270
|
}
|
239
271
|
|
240
|
-
TEST(
|
272
|
+
TEST(uncompact_withZero) {
|
241
273
|
// maxUncompactSize and uncompact both permit 0 indexes
|
242
274
|
// in the input array, and skip them.
|
243
275
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
* Copyright 2017-
|
2
|
+
* Copyright 2017-2019 Uber Technologies, Inc.
|
3
3
|
*
|
4
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
* you may not use this file except in compliance with the License.
|
@@ -168,6 +168,18 @@ SUITE(geoCoord) {
|
|
168
168
|
t_assert(_geoDistRads(&start, &out) < 0.01, "moved back to origin");
|
169
169
|
}
|
170
170
|
|
171
|
+
TEST(_geoDistRads_wrappedLongitude) {
|
172
|
+
const GeoCoord negativeLongitude = {.lat = 0, .lon = -(M_PI + M_PI_2)};
|
173
|
+
const GeoCoord zero = {.lat = 0, .lon = 0};
|
174
|
+
|
175
|
+
t_assert(fabs(M_PI_2 - _geoDistRads(&negativeLongitude, &zero)) <
|
176
|
+
EPSILON_RAD,
|
177
|
+
"Distance with wrapped longitude");
|
178
|
+
t_assert(fabs(M_PI_2 - _geoDistRads(&zero, &negativeLongitude)) <
|
179
|
+
EPSILON_RAD,
|
180
|
+
"Distance with wrapped longitude and swapped arguments");
|
181
|
+
}
|
182
|
+
|
171
183
|
TEST(doubleConstants) {
|
172
184
|
// Simple checks for ordering of values
|
173
185
|
testDecreasingFunction(H3_EXPORT(hexAreaKm2), "hexAreaKm2 ordering");
|
@@ -86,10 +86,12 @@ SUITE(h3ToLocalIj) {
|
|
86
86
|
}
|
87
87
|
|
88
88
|
TEST(ijOutOfRange) {
|
89
|
-
const int numCoords =
|
90
|
-
const CoordIJ coords[] = {{0, 0}, {1, 0},
|
89
|
+
const int numCoords = 7;
|
90
|
+
const CoordIJ coords[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0},
|
91
|
+
{4, 0}, {-4, 0}, {0, 4}};
|
91
92
|
const H3Index expected[] = {0x81283ffffffffff, 0x81293ffffffffff,
|
92
93
|
0x8150bffffffffff, 0x8151bffffffffff,
|
94
|
+
H3_INVALID_INDEX, H3_INVALID_INDEX,
|
93
95
|
H3_INVALID_INDEX};
|
94
96
|
|
95
97
|
for (int i = 0; i < numCoords; i++) {
|
@@ -64,13 +64,13 @@ SUITE(polyfill) {
|
|
64
64
|
|
65
65
|
TEST(maxPolyfillSize) {
|
66
66
|
int numHexagons = H3_EXPORT(maxPolyfillSize)(&sfGeoPolygon, 9);
|
67
|
-
t_assert(numHexagons ==
|
67
|
+
t_assert(numHexagons == 3457, "got expected max polyfill size");
|
68
68
|
|
69
69
|
numHexagons = H3_EXPORT(maxPolyfillSize)(&holeGeoPolygon, 9);
|
70
|
-
t_assert(numHexagons ==
|
70
|
+
t_assert(numHexagons == 3457, "got expected max polyfill size (hole)");
|
71
71
|
|
72
72
|
numHexagons = H3_EXPORT(maxPolyfillSize)(&emptyGeoPolygon, 9);
|
73
|
-
t_assert(numHexagons ==
|
73
|
+
t_assert(numHexagons == 3, "got expected max polyfill size (empty)");
|
74
74
|
}
|
75
75
|
|
76
76
|
TEST(polyfill) {
|
@@ -0,0 +1,58 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright 2017-2019 Uber Technologies, Inc.
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
#include <stdlib.h>
|
18
|
+
#include "algos.h"
|
19
|
+
#include "constants.h"
|
20
|
+
#include "geoCoord.h"
|
21
|
+
#include "h3Index.h"
|
22
|
+
#include "test.h"
|
23
|
+
|
24
|
+
// https://github.com/uber/h3/issues/136
|
25
|
+
|
26
|
+
static GeoCoord testVerts[] = {{0.10068990369902957, 0.8920772174196191},
|
27
|
+
{0.10032914690616246, 0.8915914753447348},
|
28
|
+
{0.10033349237998787, 0.8915860128746426},
|
29
|
+
{0.10069496685903621, 0.8920742194546231}};
|
30
|
+
static Geofence testGeofence = {.numVerts = 4, .verts = testVerts};
|
31
|
+
static GeoPolygon testPolygon;
|
32
|
+
|
33
|
+
static int countActualHexagons(H3Index* hexagons, int numHexagons) {
|
34
|
+
int actualNumHexagons = 0;
|
35
|
+
for (int i = 0; i < numHexagons; i++) {
|
36
|
+
if (hexagons[i] != 0) {
|
37
|
+
actualNumHexagons++;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
return actualNumHexagons;
|
41
|
+
}
|
42
|
+
|
43
|
+
SUITE(polyfill_gh136) {
|
44
|
+
testPolygon.geofence = testGeofence;
|
45
|
+
testPolygon.numHoles = 0;
|
46
|
+
|
47
|
+
TEST(gh136) {
|
48
|
+
int res = 13;
|
49
|
+
int numHexagons = H3_EXPORT(maxPolyfillSize)(&testPolygon, res);
|
50
|
+
H3Index* hexagons = calloc(numHexagons, sizeof(H3Index));
|
51
|
+
|
52
|
+
H3_EXPORT(polyfill)(&testPolygon, res, hexagons);
|
53
|
+
int actualNumHexagons = countActualHexagons(hexagons, numHexagons);
|
54
|
+
|
55
|
+
t_assert(actualNumHexagons == 4353, "got expected polyfill size");
|
56
|
+
free(hexagons);
|
57
|
+
}
|
58
|
+
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
* Copyright 2017-
|
2
|
+
* Copyright 2017-2019 Uber Technologies, Inc.
|
3
3
|
*
|
4
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
* you may not use this file except in compliance with the License.
|
@@ -553,4 +553,29 @@ SUITE(polygon) {
|
|
553
553
|
|
554
554
|
H3_EXPORT(destroyLinkedPolygon)(&polygon);
|
555
555
|
}
|
556
|
+
|
557
|
+
TEST(normalizeMultiPolygon_unassignedHole) {
|
558
|
+
GeoCoord verts[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};
|
559
|
+
|
560
|
+
LinkedGeoLoop* outer = malloc(sizeof(*outer));
|
561
|
+
assert(outer != NULL);
|
562
|
+
createLinkedLoop(outer, &verts[0], 4);
|
563
|
+
|
564
|
+
GeoCoord verts2[] = {{2, 2}, {3, 3}, {2, 3}};
|
565
|
+
|
566
|
+
LinkedGeoLoop* inner = malloc(sizeof(*inner));
|
567
|
+
assert(inner != NULL);
|
568
|
+
createLinkedLoop(inner, &verts2[0], 3);
|
569
|
+
|
570
|
+
LinkedGeoPolygon polygon = {0};
|
571
|
+
addLinkedLoop(&polygon, inner);
|
572
|
+
addLinkedLoop(&polygon, outer);
|
573
|
+
|
574
|
+
int result = normalizeMultiPolygon(&polygon);
|
575
|
+
|
576
|
+
t_assert(result == NORMALIZATION_ERR_UNASSIGNED_HOLES,
|
577
|
+
"Expected error code returned");
|
578
|
+
|
579
|
+
H3_EXPORT(destroyLinkedPolygon)(&polygon);
|
580
|
+
}
|
556
581
|
}
|
@@ -40,4 +40,12 @@ void h3SetToVertexGraph(const H3Index* h3Set, const int numHexes,
|
|
40
40
|
// Create a LinkedGeoPolygon from a vertex graph
|
41
41
|
void _vertexGraphToLinkedGeo(VertexGraph* graph, LinkedGeoPolygon* out);
|
42
42
|
|
43
|
+
// Internal function for polyfill that traces a geofence with hexagons of a
|
44
|
+
// specific size
|
45
|
+
int _getEdgeHexagons(const Geofence* geofence, int numHexagons, int res,
|
46
|
+
int* numSearchHexes, H3Index* search, H3Index* found);
|
47
|
+
|
48
|
+
// The new polyfill algorithm. Separated out because it can theoretically fail
|
49
|
+
int _polyfillInternal(const GeoPolygon* geoPolygon, int res, H3Index* out);
|
50
|
+
|
43
51
|
#endif
|
@@ -37,6 +37,8 @@ bool bboxIsTransmeridian(const BBox* bbox);
|
|
37
37
|
void bboxCenter(const BBox* bbox, GeoCoord* center);
|
38
38
|
bool bboxContains(const BBox* bbox, const GeoCoord* point);
|
39
39
|
bool bboxEquals(const BBox* b1, const BBox* b2);
|
40
|
-
int
|
40
|
+
int bboxHexEstimate(const BBox* bbox, int res);
|
41
|
+
int lineHexEstimate(const GeoCoord* origin, const GeoCoord* destination,
|
42
|
+
int res);
|
41
43
|
|
42
44
|
#endif
|
@@ -41,6 +41,7 @@
|
|
41
41
|
#define HEX_RANGE_SUCCESS 0
|
42
42
|
#define HEX_RANGE_PENTAGON 1
|
43
43
|
#define HEX_RANGE_K_SUBSEQUENCE 2
|
44
|
+
#define MAX_ONE_RING_SIZE 7
|
44
45
|
|
45
46
|
/**
|
46
47
|
* Directions used for traversing a hexagonal ring counterclockwise around
|
@@ -623,9 +624,8 @@ int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index* out) {
|
|
623
624
|
* maxPolyfillSize returns the number of hexagons to allocate space for when
|
624
625
|
* performing a polyfill on the given GeoJSON-like data structure.
|
625
626
|
*
|
626
|
-
*
|
627
|
-
*
|
628
|
-
* a Python application? ;)
|
627
|
+
* The size is the maximum of either the number of points in the geofence or the
|
628
|
+
* number of hexagons in the bounding box of the geofence.
|
629
629
|
*
|
630
630
|
* @param geoPolygon A GeoJSON-like data structure indicating the poly to fill
|
631
631
|
* @param res Hexagon resolution (0-15)
|
@@ -634,12 +634,18 @@ int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index* out) {
|
|
634
634
|
int H3_EXPORT(maxPolyfillSize)(const GeoPolygon* geoPolygon, int res) {
|
635
635
|
// Get the bounding box for the GeoJSON-like struct
|
636
636
|
BBox bbox;
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
//
|
641
|
-
// the
|
642
|
-
|
637
|
+
const Geofence geofence = geoPolygon->geofence;
|
638
|
+
bboxFromGeofence(&geofence, &bbox);
|
639
|
+
int numHexagons = bboxHexEstimate(&bbox, res);
|
640
|
+
// This algorithm assumes that the number of vertices is usually less than
|
641
|
+
// the number of hexagons, but when it's wrong, this will keep it from
|
642
|
+
// failing
|
643
|
+
int totalVerts = geofence.numVerts;
|
644
|
+
for (int i = 0; i < geoPolygon->numHoles; i++) {
|
645
|
+
totalVerts += geoPolygon->holes[i].numVerts;
|
646
|
+
}
|
647
|
+
if (numHexagons < totalVerts) numHexagons = totalVerts;
|
648
|
+
return numHexagons;
|
643
649
|
}
|
644
650
|
|
645
651
|
/**
|
@@ -647,15 +653,108 @@ int H3_EXPORT(maxPolyfillSize)(const GeoPolygon* geoPolygon, int res) {
|
|
647
653
|
* zeroed memory, and fills it with the hexagons that are contained by
|
648
654
|
* the GeoJSON-like data structure.
|
649
655
|
*
|
650
|
-
*
|
651
|
-
*
|
652
|
-
*
|
656
|
+
* This implementation traces the GeoJSON geofence(s) in cartesian space with
|
657
|
+
* hexagons, tests them and their neighbors to be contained by the geofence(s),
|
658
|
+
* and then any newly found hexagons are used to test again until no new
|
659
|
+
* hexagons are found.
|
653
660
|
*
|
654
661
|
* @param geoPolygon The geofence and holes defining the relevant area
|
655
662
|
* @param res The Hexagon resolution (0-15)
|
656
663
|
* @param out The slab of zeroed memory to write to. Assumed to be big enough.
|
657
664
|
*/
|
658
665
|
void H3_EXPORT(polyfill)(const GeoPolygon* geoPolygon, int res, H3Index* out) {
|
666
|
+
// TODO: Eliminate this wrapper with the H3 4.0.0 release
|
667
|
+
int failure = _polyfillInternal(geoPolygon, res, out);
|
668
|
+
// The polyfill algorithm can theoretically fail if the allocated memory is
|
669
|
+
// not large enough for the polygon, but this should be impossible given the
|
670
|
+
// conservative overestimation of the number of hexagons possible.
|
671
|
+
// LCOV_EXCL_START
|
672
|
+
if (failure) {
|
673
|
+
int numHexagons = H3_EXPORT(maxPolyfillSize)(geoPolygon, res);
|
674
|
+
for (int i = 0; i < numHexagons; i++) out[i] = H3_INVALID_INDEX;
|
675
|
+
}
|
676
|
+
// LCOV_EXCL_STOP
|
677
|
+
}
|
678
|
+
|
679
|
+
/**
|
680
|
+
* _getEdgeHexagons takes a given geofence ring (either the main geofence or
|
681
|
+
* one of the holes) and traces it with hexagons and updates the search and
|
682
|
+
* found memory blocks. This is used for determining the initial hexagon set
|
683
|
+
* for the polyfill algorithm to execute on.
|
684
|
+
*
|
685
|
+
* @param geofence The geofence (or hole) to be traced
|
686
|
+
* @param numHexagons The maximum number of hexagons possible for the geofence
|
687
|
+
* (also the bounds of the search and found arrays)
|
688
|
+
* @param res The hexagon resolution (0-15)
|
689
|
+
* @param numSearchHexes The number of hexagons found so far to be searched
|
690
|
+
* @param search The block of memory containing the hexagons to search from
|
691
|
+
* @param found The block of memory containing the hexagons found from the
|
692
|
+
* search
|
693
|
+
*
|
694
|
+
* @return An error code if the hash function cannot insert a found hexagon
|
695
|
+
* into the found array.
|
696
|
+
*/
|
697
|
+
int _getEdgeHexagons(const Geofence* geofence, int numHexagons, int res,
|
698
|
+
int* numSearchHexes, H3Index* search, H3Index* found) {
|
699
|
+
for (int i = 0; i < geofence->numVerts; i++) {
|
700
|
+
GeoCoord origin = geofence->verts[i];
|
701
|
+
GeoCoord destination = i == geofence->numVerts - 1
|
702
|
+
? geofence->verts[0]
|
703
|
+
: geofence->verts[i + 1];
|
704
|
+
const int numHexesEstimate =
|
705
|
+
lineHexEstimate(&origin, &destination, res);
|
706
|
+
for (int j = 0; j < numHexesEstimate; j++) {
|
707
|
+
GeoCoord interpolate;
|
708
|
+
interpolate.lat =
|
709
|
+
(origin.lat * (numHexesEstimate - j) / numHexesEstimate) +
|
710
|
+
(destination.lat * j / numHexesEstimate);
|
711
|
+
interpolate.lon =
|
712
|
+
(origin.lon * (numHexesEstimate - j) / numHexesEstimate) +
|
713
|
+
(destination.lon * j / numHexesEstimate);
|
714
|
+
H3Index pointHex = H3_EXPORT(geoToH3)(&interpolate, res);
|
715
|
+
// A simple hash to store the hexagon, or move to another place if
|
716
|
+
// needed
|
717
|
+
int loc = (int)(pointHex % numHexagons);
|
718
|
+
int loopCount = 0;
|
719
|
+
while (found[loc] != 0) {
|
720
|
+
// If this conditional is reached, the `found` memory block is
|
721
|
+
// too small for the given polygon. This should not happen.
|
722
|
+
if (loopCount > numHexagons) return -1; // LCOV_EXCL_LINE
|
723
|
+
if (found[loc] == pointHex)
|
724
|
+
break; // At least two points of the geofence index to the
|
725
|
+
// same cell
|
726
|
+
loc = (loc + 1) % numHexagons;
|
727
|
+
loopCount++;
|
728
|
+
}
|
729
|
+
if (found[loc] == pointHex)
|
730
|
+
continue; // Skip this hex, already exists in the found hash
|
731
|
+
// Otherwise, set it in the found hash for now
|
732
|
+
found[loc] = pointHex;
|
733
|
+
|
734
|
+
search[*numSearchHexes] = pointHex;
|
735
|
+
(*numSearchHexes)++;
|
736
|
+
}
|
737
|
+
}
|
738
|
+
return 0;
|
739
|
+
}
|
740
|
+
|
741
|
+
/**
|
742
|
+
* _polyfillInternal traces the provided geoPolygon data structure with hexagons
|
743
|
+
* and then iteratively searches through these hexagons and their immediate
|
744
|
+
* neighbors to see if they are contained within the polygon or not. Those that
|
745
|
+
* are found are added to the out array as well as the found array. Once all
|
746
|
+
* hexagons to search are checked, the found hexagons become the new search
|
747
|
+
* array and the found array is wiped and the process repeats until no new
|
748
|
+
* hexagons can be found.
|
749
|
+
*
|
750
|
+
* @param geoPolygon The geofence and holes defining the relevant area
|
751
|
+
* @param res The Hexagon resolution (0-15)
|
752
|
+
* @param out The slab of zeroed memory to write to. Assumed to be big enough.
|
753
|
+
*
|
754
|
+
* @return An error code if any of the hash operations fails to insert a hexagon
|
755
|
+
* into an array of memory.
|
756
|
+
*/
|
757
|
+
int _polyfillInternal(const GeoPolygon* geoPolygon, int res, H3Index* out) {
|
659
758
|
// One of the goals of the polyfill algorithm is that two adjacent polygons
|
660
759
|
// with zero overlap have zero overlapping hexagons. That the hexagons are
|
661
760
|
// uniquely assigned. There are a few approaches to take here, such as
|
@@ -677,36 +776,139 @@ void H3_EXPORT(polyfill)(const GeoPolygon* geoPolygon, int res, H3Index* out) {
|
|
677
776
|
BBox* bboxes = malloc((geoPolygon->numHoles + 1) * sizeof(BBox));
|
678
777
|
assert(bboxes != NULL);
|
679
778
|
bboxesFromGeoPolygon(geoPolygon, bboxes);
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
//
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
//
|
693
|
-
//
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
779
|
+
|
780
|
+
// Get the estimated number of hexagons and allocate some temporary memory
|
781
|
+
// for the hexagons
|
782
|
+
int numHexagons = H3_EXPORT(maxPolyfillSize)(geoPolygon, res);
|
783
|
+
H3Index* search = calloc(numHexagons, sizeof(H3Index));
|
784
|
+
H3Index* found = calloc(numHexagons, sizeof(H3Index));
|
785
|
+
|
786
|
+
// Some metadata for tracking the state of the search and found memory
|
787
|
+
// blocks
|
788
|
+
int numSearchHexes = 0;
|
789
|
+
int numFoundHexes = 0;
|
790
|
+
|
791
|
+
// 1. Trace the hexagons along the polygon defining the outer geofence and
|
792
|
+
// add them to the search hash. The hexagon containing the geofence point
|
793
|
+
// may or may not be contained by the geofence (as the hexagon's center
|
794
|
+
// point may be outside of the boundary.)
|
795
|
+
const Geofence geofence = geoPolygon->geofence;
|
796
|
+
int failure = _getEdgeHexagons(&geofence, numHexagons, res, &numSearchHexes,
|
797
|
+
search, found);
|
798
|
+
// If this branch is reached, we have exceeded the maximum number of
|
799
|
+
// hexagons possible and need to clean up the allocated memory.
|
800
|
+
// LCOV_EXCL_START
|
801
|
+
if (failure) {
|
802
|
+
free(search);
|
803
|
+
free(found);
|
804
|
+
free(bboxes);
|
805
|
+
return failure;
|
806
|
+
}
|
807
|
+
// LCOV_EXCL_STOP
|
808
|
+
|
809
|
+
// 2. Iterate over all holes, trace the polygons defining the holes with
|
810
|
+
// hexagons and add to only the search hash. We're going to temporarily use
|
811
|
+
// the `found` hash to use for dedupe purposes and then re-zero it once
|
812
|
+
// we're done here, otherwise we'd have to scan the whole set on each insert
|
813
|
+
// to make sure there's no duplicates, which is very inefficient.
|
814
|
+
for (int i = 0; i < geoPolygon->numHoles; i++) {
|
815
|
+
Geofence* hole = &(geoPolygon->holes[i]);
|
816
|
+
failure = _getEdgeHexagons(hole, numHexagons, res, &numSearchHexes,
|
817
|
+
search, found);
|
818
|
+
// If this branch is reached, we have exceeded the maximum number of
|
819
|
+
// hexagons possible and need to clean up the allocated memory.
|
820
|
+
// LCOV_EXCL_START
|
821
|
+
if (failure) {
|
822
|
+
free(search);
|
823
|
+
free(found);
|
824
|
+
free(bboxes);
|
825
|
+
return failure;
|
698
826
|
}
|
699
|
-
//
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
827
|
+
// LCOV_EXCL_STOP
|
828
|
+
}
|
829
|
+
|
830
|
+
// 3. Re-zero the found hash so it can be used in the main loop below
|
831
|
+
for (int i = 0; i < numHexagons; i++) found[i] = 0;
|
832
|
+
|
833
|
+
// 4. Begin main loop. While the search hash is not empty do the following
|
834
|
+
while (numSearchHexes > 0) {
|
835
|
+
// Iterate through all hexagons in the current search hash, then loop
|
836
|
+
// through all neighbors and test Point-in-Poly, if point-in-poly
|
837
|
+
// succeeds, add to out and found hashes if not already there.
|
838
|
+
int currentSearchNum = 0;
|
839
|
+
int i = 0;
|
840
|
+
while (currentSearchNum < numSearchHexes) {
|
841
|
+
H3Index ring[MAX_ONE_RING_SIZE] = {0};
|
842
|
+
H3Index searchHex = search[i];
|
843
|
+
H3_EXPORT(kRing)(searchHex, 1, ring);
|
844
|
+
for (int j = 0; j < MAX_ONE_RING_SIZE; j++) {
|
845
|
+
if (ring[j] == H3_INVALID_INDEX) {
|
846
|
+
continue; // Skip if this was a pentagon and only had 5
|
847
|
+
// neighbors
|
848
|
+
}
|
849
|
+
|
850
|
+
H3Index hex = ring[j];
|
851
|
+
|
852
|
+
// A simple hash to store the hexagon, or move to another place
|
853
|
+
// if needed. This MUST be done before the point-in-poly check
|
854
|
+
// since that's far more expensive
|
855
|
+
int loc = (int)(hex % numHexagons);
|
856
|
+
int loopCount = 0;
|
857
|
+
while (out[loc] != 0) {
|
858
|
+
// If this branch is reached, we have exceeded the maximum
|
859
|
+
// number of hexagons possible and need to clean up the
|
860
|
+
// allocated memory.
|
861
|
+
// LCOV_EXCL_START
|
862
|
+
if (loopCount > numHexagons) {
|
863
|
+
free(search);
|
864
|
+
free(found);
|
865
|
+
free(bboxes);
|
866
|
+
return -1;
|
867
|
+
}
|
868
|
+
// LCOV_EXCL_STOP
|
869
|
+
if (out[loc] == hex) break; // Skip duplicates found
|
870
|
+
loc = (loc + 1) % numHexagons;
|
871
|
+
loopCount++;
|
872
|
+
}
|
873
|
+
if (out[loc] == hex) {
|
874
|
+
continue; // Skip this hex, already exists in the out hash
|
875
|
+
}
|
876
|
+
|
877
|
+
// Check if the hexagon is in the polygon or not
|
878
|
+
GeoCoord hexCenter;
|
879
|
+
H3_EXPORT(h3ToGeo)(hex, &hexCenter);
|
880
|
+
|
881
|
+
// If not, skip
|
882
|
+
if (!pointInsidePolygon(geoPolygon, bboxes, &hexCenter)) {
|
883
|
+
continue;
|
884
|
+
}
|
885
|
+
|
886
|
+
// Otherwise set it in the output array
|
887
|
+
out[loc] = hex;
|
888
|
+
|
889
|
+
// Set the hexagon in the found hash
|
890
|
+
found[numFoundHexes] = hex;
|
891
|
+
numFoundHexes++;
|
892
|
+
}
|
893
|
+
currentSearchNum++;
|
894
|
+
i++;
|
707
895
|
}
|
896
|
+
|
897
|
+
// Swap the search and found pointers, copy the found hex count to the
|
898
|
+
// search hex count, and zero everything related to the found memory.
|
899
|
+
H3Index* temp = search;
|
900
|
+
search = found;
|
901
|
+
found = temp;
|
902
|
+
for (int j = 0; j < numSearchHexes; j++) found[j] = 0;
|
903
|
+
numSearchHexes = numFoundHexes;
|
904
|
+
numFoundHexes = 0;
|
905
|
+
// Repeat until no new hexagons are found
|
708
906
|
}
|
907
|
+
// The out memory structure should be complete, end it here
|
709
908
|
free(bboxes);
|
909
|
+
free(search);
|
910
|
+
free(found);
|
911
|
+
return 0;
|
710
912
|
}
|
711
913
|
|
712
914
|
/**
|
@@ -88,34 +88,59 @@ double _hexRadiusKm(H3Index h3Index) {
|
|
88
88
|
}
|
89
89
|
|
90
90
|
/**
|
91
|
-
*
|
92
|
-
*
|
93
|
-
*
|
94
|
-
* @param
|
95
|
-
* @
|
91
|
+
* bboxHexEstimate returns an estimated number of hexagons that fit
|
92
|
+
* within the cartesian-projected bounding box
|
93
|
+
*
|
94
|
+
* @param bbox the bounding box to estimate the hexagon fill level
|
95
|
+
* @param res the resolution of the H3 hexagons to fill the bounding box
|
96
|
+
* @return the estimated number of hexagons to fill the bounding box
|
96
97
|
*/
|
97
|
-
int
|
98
|
-
//
|
99
|
-
|
100
|
-
|
98
|
+
int bboxHexEstimate(const BBox* bbox, int res) {
|
99
|
+
// Get the area of the pentagon as the maximally-distorted area possible
|
100
|
+
H3Index pentagons[12] = {0};
|
101
|
+
H3_EXPORT(getPentagonIndexes)(res, pentagons);
|
102
|
+
double pentagonRadiusKm = _hexRadiusKm(pentagons[0]);
|
103
|
+
// Area of a regular hexagon is 3/2*sqrt(3) * r * r
|
104
|
+
// The pentagon has the most distortion (smallest edges) and shares its
|
105
|
+
// edges with hexagons, so the most-distorted hexagons have this area
|
106
|
+
double pentagonAreaKm2 =
|
107
|
+
2.59807621135 * pentagonRadiusKm * pentagonRadiusKm;
|
101
108
|
|
102
|
-
//
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
109
|
+
// Then get the area of the bounding box of the geofence in question
|
110
|
+
GeoCoord p1, p2;
|
111
|
+
p1.lat = bbox->north;
|
112
|
+
p1.lon = bbox->east;
|
113
|
+
p2.lat = bbox->south;
|
114
|
+
p2.lon = bbox->east;
|
115
|
+
double h = _geoDistKm(&p1, &p2);
|
116
|
+
p2.lat = bbox->north;
|
117
|
+
p2.lon = bbox->west;
|
118
|
+
double w = _geoDistKm(&p1, &p2);
|
107
119
|
|
108
|
-
//
|
109
|
-
|
110
|
-
|
120
|
+
// Divide the two to get an estimate of the number of hexagons needed
|
121
|
+
int estimate = (int)ceil(w * h / pentagonAreaKm2);
|
122
|
+
if (estimate == 0) estimate = 1;
|
123
|
+
return estimate;
|
124
|
+
}
|
111
125
|
|
112
|
-
|
113
|
-
|
126
|
+
/**
|
127
|
+
* lineHexEstimate returns an estimated number of hexagons that trace
|
128
|
+
* the cartesian-projected line
|
129
|
+
*
|
130
|
+
* @param origin the origin coordinates
|
131
|
+
* @param destination the destination coordinates
|
132
|
+
* @param res the resolution of the H3 hexagons to trace the line
|
133
|
+
* @return the estimated number of hexagons required to trace the line
|
134
|
+
*/
|
135
|
+
int lineHexEstimate(const GeoCoord* origin, const GeoCoord* destination,
|
136
|
+
int res) {
|
137
|
+
// Get the area of the pentagon as the maximally-distorted area possible
|
138
|
+
H3Index pentagons[12] = {0};
|
139
|
+
H3_EXPORT(getPentagonIndexes)(res, pentagons);
|
140
|
+
double pentagonRadiusKm = _hexRadiusKm(pentagons[0]);
|
114
141
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
// Rounded *up* to guarantee containment
|
120
|
-
return (int)ceil(bboxRadiusKm / (1.5 * centerHexRadiusKm));
|
142
|
+
double dist = _geoDistKm(origin, destination);
|
143
|
+
int estimate = (int)ceil(dist / (2 * pentagonRadiusKm));
|
144
|
+
if (estimate == 0) estimate = 1;
|
145
|
+
return estimate;
|
121
146
|
}
|
@@ -266,6 +266,9 @@ H3Index H3_EXPORT(h3ToCenterChild)(H3Index h, int childRes) {
|
|
266
266
|
*/
|
267
267
|
int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet,
|
268
268
|
const int numHexes) {
|
269
|
+
if (numHexes == 0) {
|
270
|
+
return 0;
|
271
|
+
}
|
269
272
|
int res = H3_GET_RESOLUTION(h3Set[0]);
|
270
273
|
if (res == 0) {
|
271
274
|
// No compaction possible, just copy the set to output
|
data/lib/h3/version.rb
CHANGED
data/spec/region_spec.rb
CHANGED
@@ -42,7 +42,7 @@ RSpec.describe H3 do
|
|
42
42
|
File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json"))
|
43
43
|
end
|
44
44
|
let(:resolution) { 9 }
|
45
|
-
let(:expected_count) {
|
45
|
+
let(:expected_count) { 36_193 }
|
46
46
|
|
47
47
|
subject(:max_polyfill_size) { H3.max_polyfill_size(geojson, resolution) }
|
48
48
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: h3
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.6.
|
4
|
+
version: 3.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lachlan Laycock
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-11-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ffi
|
@@ -248,6 +248,7 @@ files:
|
|
248
248
|
- ext/h3/src/src/apps/testapps/testMaxH3ToChildrenSize.c
|
249
249
|
- ext/h3/src/src/apps/testapps/testPentagonIndexes.c
|
250
250
|
- ext/h3/src/src/apps/testapps/testPolyfill.c
|
251
|
+
- ext/h3/src/src/apps/testapps/testPolyfill_GH136.c
|
251
252
|
- ext/h3/src/src/apps/testapps/testPolygon.c
|
252
253
|
- ext/h3/src/src/apps/testapps/testVec2d.c
|
253
254
|
- ext/h3/src/src/apps/testapps/testVec3d.c
|