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 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