h3 3.6.0 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4344398c78a4d188c270838673195d9e16e1a400ab157ec34fa45a006fc17c2b
4
- data.tar.gz: 0ee405d8aefdb3fa062079903ae586da85598b2a12e87c4b029c11aa3d1c73f2
3
+ metadata.gz: 32f9595b2e8746f59aaef1c082fc3527a7eb3d7594d7eac1a59cb5875df6af3d
4
+ data.tar.gz: 8fd9c4ed2317a0211b4d3fa6360aede580bff5db691605b7fdd33b299720ba3f
5
5
  SHA512:
6
- metadata.gz: c385c0805d7b8e59c2610048ff71aacaf9e4b5a8aae21385a36ae42d47f036eab812296752ff108146655ed2fc1b87800246889d3393bf1a6940dfce43e34a4e
7
- data.tar.gz: f8aeaf98281a57718266fb5eb259d269e1dbf4804c02fb0af25327c39594084eba87fbdb504446066330b5c57368aaedab856c45bf0a437726fd6d95d7b159b0
6
+ metadata.gz: 4e0a51a2bdcf92096406af99d3823359768b99291f96be9ac6d113363c09efb71513cf09944eca1155549b63bbab4c628e2a6f89f8d77817df4029155375072e
7
+ data.tar.gz: 1c672e3d6d2262dee4bae5bf65587a870c9053b1815ec7ee34798fae228aed5bd338d57883ebb594f81b6c959006af86c460ddca473e4db401631e2400a11dcd
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- h3 (3.6.0)
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.1)
20
+ ffi (1.11.2)
21
21
  json (2.1.0)
22
22
  rake (12.3.2)
23
- rgeo (2.1.0)
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.9)
49
+ zeitwerk (2.2.1)
50
50
 
51
51
  PLATFORMS
52
52
  ruby
@@ -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)
@@ -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> 1 res${resolution}cells.kml \"Res ${resolution} Cells\" > KML/res${resolution}cells.kml"
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> 1 res${resolution}centers.kml \"Res ${resolution} Centers\" > KML/res${resolution}centers.kml"
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)
@@ -1,4 +1,4 @@
1
- <img align="right" src="https://uber.github.io/res/h3Logo-color.svg" alt="H3 Logo" width="200">
1
+ <img align="right" src="https://uber.github.io/img/h3Logo-color.svg" alt="H3 Logo" width="200">
2
2
 
3
3
  # H3: A Hexagonal Hierarchical Geospatial Indexing System
4
4
 
@@ -1 +1 @@
1
- 3.6.0
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&#35;
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-2018 Uber Technologies, Inc.
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(uncompactZero) {
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-2018 Uber Technologies, Inc.
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 = 5;
90
- const CoordIJ coords[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 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 == 3169, "got expected max polyfill size");
67
+ t_assert(numHexagons == 3457, "got expected max polyfill size");
68
68
 
69
69
  numHexagons = H3_EXPORT(maxPolyfillSize)(&holeGeoPolygon, 9);
70
- t_assert(numHexagons == 3169, "got expected max polyfill size (hole)");
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 == 1, "got expected max polyfill size (empty)");
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-2018 Uber Technologies, Inc.
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 bboxHexRadius(const BBox* bbox, int res);
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
- * Currently a laughably padded response, being a k-ring that wholly contains
627
- * a bounding box of the GeoJSON, but still less wasted memory than initializing
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
- bboxFromGeofence(&geoPolygon->geofence, &bbox);
638
- int minK = bboxHexRadius(&bbox, res);
639
-
640
- // The total number of hexagons to allocate can now be determined by
641
- // the k-ring hex allocation helper function.
642
- return H3_EXPORT(maxKringSize)(minK);
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
- * The current implementation is very primitive and slow, but correct,
651
- * performing a point-in-poly operation on every hexagon in a k-ring defined
652
- * around the given geofence.
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
- int minK = bboxHexRadius(&bboxes[0], res);
681
- int numHexagons = H3_EXPORT(maxKringSize)(minK);
682
-
683
- // Get the center hex
684
- GeoCoord center;
685
- bboxCenter(&bboxes[0], &center);
686
- H3Index centerH3 = H3_EXPORT(geoToH3)(&center, res);
687
-
688
- // From here on it works differently, first we get all potential
689
- // hexagons inserted into the available memory
690
- H3_EXPORT(kRing)(centerH3, minK, out);
691
-
692
- // Next we iterate through each hexagon, and test its center point to see if
693
- // it's contained in the GeoJSON-like struct
694
- for (int i = 0; i < numHexagons; i++) {
695
- // Skip records that are already zeroed
696
- if (out[i] == 0) {
697
- continue;
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
- // Check if hexagon is inside of polygon
700
- GeoCoord hexCenter;
701
- H3_EXPORT(h3ToGeo)(out[i], &hexCenter);
702
- hexCenter.lat = constrainLat(hexCenter.lat);
703
- hexCenter.lon = constrainLng(hexCenter.lon);
704
- // And remove from list if not
705
- if (!pointInsidePolygon(geoPolygon, bboxes, &hexCenter)) {
706
- out[i] = H3_INVALID_INDEX;
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
- * Get the radius of the bbox in hexagons - i.e. the radius of a k-ring centered
92
- * on the bbox center and covering the entire bbox.
93
- * @param bbox Bounding box to measure
94
- * @param res Resolution of hexagons to use in measurement
95
- * @return Radius in hexagons
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 bboxHexRadius(const BBox* bbox, int res) {
98
- // Determine the center of the bounding box
99
- GeoCoord center;
100
- bboxCenter(bbox, &center);
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
- // Use a vertex on the side closest to the equator, to ensure the longest
103
- // radius in cases with significant distortion. East/west is arbitrary.
104
- double lat =
105
- fabs(bbox->north) > fabs(bbox->south) ? bbox->south : bbox->north;
106
- GeoCoord vertex = {lat, bbox->east};
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
- // Determine the length of the bounding box "radius" to then use
109
- // as a circle on the earth that the k-rings must be greater than
110
- double bboxRadiusKm = _geoDistKm(&center, &vertex);
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
- // Determine the radius of the center hexagon
113
- double centerHexRadiusKm = _hexRadiusKm(H3_EXPORT(geoToH3)(&center, res));
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
- // The closest point along a hexagon drawn through the center points
116
- // of a k-ring aggregation is exactly 1.5 radii of the hexagon. For
117
- // any orientation of the GeoJSON encased in a circle defined by the
118
- // bounding box radius and center, it is guaranteed to fit in this k-ring
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
@@ -1,3 +1,3 @@
1
1
  module H3
2
- VERSION = "3.6.0".freeze
2
+ VERSION = "3.6.1".freeze
3
3
  end
@@ -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) { 33_391 }
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.0
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-08-14 00:00:00.000000000 Z
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