h3 3.2.0

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.
@@ -0,0 +1,287 @@
1
+ module H3
2
+ # Grid traversal functions
3
+ #
4
+ # @see https://uber.github.io/h3/#/documentation/api-reference/traversal
5
+ module Traversal
6
+ extend H3::Bindings::Base
7
+
8
+ # @!method max_kring_size(k)
9
+ #
10
+ # Derive the maximum k-ring size for distance k.
11
+ #
12
+ # @param [Integer] k K value.
13
+ #
14
+ # @example Derive the maximum k-ring size for k=5
15
+ # H3.max_kring_size(5)
16
+ # 91
17
+ #
18
+ # @return [Integer] Maximum k-ring size.
19
+ attach_function :max_kring_size, :maxKringSize, [ :int ], :int
20
+
21
+ # @!method h3_distance(origin, h3_index)
22
+ #
23
+ # Derive the distance between two H3 indexes.
24
+ #
25
+ # @param [Integer] origin Origin H3 index
26
+ # @param [Integer] h3_index H3 index
27
+ #
28
+ # @example Derive the distance between two H3 indexes.
29
+ # H3.h3_distance(617700169983721471, 617700169959866367)
30
+ # 5
31
+ #
32
+ # @return [Integer] Distance between indexes.
33
+ attach_function :h3_distance, :h3Distance, [ :h3_index, :h3_index], :int
34
+
35
+ # Derives H3 indexes within k distance of the origin H3 index.
36
+ #
37
+ # Similar to {k_ring}, except that an error is raised when one of the indexes
38
+ # returned is a pentagon or is in the pentagon distortion area.
39
+ #
40
+ # k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0
41
+ # and all neighboring indexes, and so on.
42
+ #
43
+ # Output is inserted into the array in order of increasing distance from the origin.
44
+ #
45
+ # @param [Integer] origin Origin H3 index
46
+ # @param [Integer] k K distance.
47
+ #
48
+ # @example Derive the hex range for a given H3 index with k of 0.
49
+ # H3.hex_range(617700169983721471, 0)
50
+ # [617700169983721471]
51
+ #
52
+ # @example Derive the hex range for a given H3 index with k of 1.
53
+ # H3.hex_range(617700169983721471, 1)
54
+ # [
55
+ # 617700169983721471, 617700170047946751, 617700169984245759,
56
+ # 617700169982672895, 617700169983983615, 617700170044276735,
57
+ # 617700170044014591
58
+ # ]
59
+ #
60
+ # @raise [ArgumentError] Raised if the range contains a pentagon.
61
+ #
62
+ # @return [Array<Integer>] Array of H3 indexes within the k-range.
63
+ def hex_range(origin, k)
64
+ max_hexagons = max_kring_size(k)
65
+ hexagons = FFI::MemoryPointer.new(:ulong_long, max_hexagons)
66
+ pentagonal_distortion = Bindings::Private.hex_range(origin, k, hexagons)
67
+ raise(ArgumentError, "Specified hexagon range contains a pentagon") if pentagonal_distortion
68
+ hexagons.read_array_of_ulong_long(max_hexagons).reject(&:zero?)
69
+ end
70
+
71
+ # Derives H3 indexes within k distance of the origin H3 index.
72
+ #
73
+ # k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0
74
+ # and all neighboring indexes, and so on.
75
+ #
76
+ # @param [Integer] origin Origin H3 index
77
+ # @param [Integer] k K distance.
78
+ #
79
+ # @example Derive the k-ring for a given H3 index with k of 0.
80
+ # H3.k_ring(617700169983721471, 0)
81
+ # [617700169983721471]
82
+ #
83
+ # @example Derive the k-ring for a given H3 index with k of 1.
84
+ # H3.k_ring(617700169983721471, 1)
85
+ # [
86
+ # 617700169983721471, 617700170047946751, 617700169984245759,
87
+ # 617700169982672895, 617700169983983615, 617700170044276735,
88
+ # 617700170044014591
89
+ # ]
90
+ #
91
+ # @return [Array<Integer>] Array of H3 indexes within the k-range.
92
+ def k_ring(origin, k)
93
+ max_hexagons = max_kring_size(k)
94
+ hexagons = FFI::MemoryPointer.new(:ulong_long, max_hexagons)
95
+ Bindings::Private.k_ring(origin, k, hexagons)
96
+ hexagons.read_array_of_ulong_long(max_hexagons).reject(&:zero?)
97
+ end
98
+
99
+ # Derives the hollow hexagonal ring centered at origin with sides of length k.
100
+ #
101
+ # An error is raised when one of the indexes returned is a pentagon or is
102
+ # in the pentagon distortion area.
103
+ #
104
+ # @param [Integer] origin Origin H3 index.
105
+ # @param [Integer] k K distance.
106
+ #
107
+ # @example Derive the hex ring for the H3 index at k = 1
108
+ # H3.hex_ring(617700169983721471, 1)
109
+ # [
110
+ # 617700170044014591, 617700170047946751, 617700169984245759,
111
+ # 617700169982672895, 617700169983983615, 617700170044276735
112
+ # ]
113
+ #
114
+ # @raise [ArgumentError] Raised if the hex ring contains a pentagon.
115
+ #
116
+ # @return [Array<Integer>] Array of H3 indexes within the hex ring.
117
+ def hex_ring(origin, k)
118
+ max_hexagons = max_hex_ring_size(k)
119
+ hexagons = FFI::MemoryPointer.new(:ulong_long, max_hexagons)
120
+ pentagonal_distortion = Bindings::Private.hex_ring(origin, k, hexagons)
121
+ raise(ArgumentError, "The hex ring contains a pentagon") if pentagonal_distortion
122
+ hexagons.read_array_of_ulong_long(max_hexagons).reject(&:zero?)
123
+ end
124
+
125
+ # Derive the maximum hex ring size for a given distance k.
126
+ #
127
+ # NOTE: This method is not part of the H3 API and is added to this binding for convenience.
128
+ #
129
+ # @param [Integer] k K distance.
130
+ #
131
+ # @example Derive maximum hex ring size for k distance 6.
132
+ # H3.max_hex_ring_size(6)
133
+ # 36
134
+ #
135
+ # @return [Integer] Maximum hex ring size.
136
+ def max_hex_ring_size(k)
137
+ k.zero? ? 1 : 6 * k
138
+ end
139
+
140
+ # Derives H3 indexes within k distance for each H3 index in the set.
141
+ #
142
+ # @param [Array<Integer>] h3_set Set of H3 indexes
143
+ # @param [Integer] k K distance.
144
+ # @param [Boolean] grouped Whether to group the output. Default true.
145
+ #
146
+ # @example Derive the hex ranges for a given H3 set with k of 0.
147
+ # H3.hex_ranges([617700169983721471, 617700169982672895], 1)
148
+ # {
149
+ # 617700169983721471 => [
150
+ # [617700169983721471],
151
+ # [
152
+ # 617700170047946751, 617700169984245759, 617700169982672895,
153
+ # 617700169983983615, 617700170044276735, 617700170044014591
154
+ # ]
155
+ # ],
156
+ # 617700169982672895 = > [
157
+ # [617700169982672895],
158
+ # [
159
+ # 617700169984245759, 617700169983197183, 617700169983459327,
160
+ # 617700169982935039, 617700169983983615, 617700169983721471
161
+ # ]
162
+ # ]
163
+ # }
164
+ #
165
+ # @example Derive the hex ranges for a given H3 set with k of 0 ungrouped.
166
+ # H3.hex_ranges([617700169983721471, 617700169982672895], 1, grouped: false)
167
+ # [
168
+ # 617700169983721471, 617700170047946751, 617700169984245759,
169
+ # 617700169982672895, 617700169983983615, 617700170044276735,
170
+ # 617700170044014591, 617700169982672895, 617700169984245759,
171
+ # 617700169983197183, 617700169983459327, 617700169982935039,
172
+ # 617700169983983615, 617700169983721471
173
+ # ]
174
+ #
175
+ # @raise [ArgumentError] Raised if any of the ranges contains a pentagon.
176
+ #
177
+ # @see #hex_range
178
+ #
179
+ # @return [Hash] Hash of H3 index keys, with array values grouped by k-ring.
180
+ def hex_ranges(h3_set, k, grouped: true)
181
+ h3_range_indexes = hex_ranges_ungrouped(h3_set, k)
182
+ return h3_range_indexes unless grouped
183
+ out = {}
184
+ h3_range_indexes.each_slice(max_kring_size(k)).each do |indexes|
185
+ h3_index = indexes.first
186
+
187
+ out[h3_index] = 0.upto(k).map do |j|
188
+ start = j == 0 ? 0 : max_kring_size(j-1)
189
+ length = max_hex_ring_size(j)
190
+ indexes.slice(start, length)
191
+ end
192
+ end
193
+ out
194
+ end
195
+
196
+ # Derives the hex range for the given origin at k distance, sub-grouped by distance.
197
+ #
198
+ # @param [Integer] origin Origin H3 index.
199
+ # @param [Integer] k K distance.
200
+ #
201
+ # @example Derive hex range at distance 2
202
+ # H3.hex_range_distances(617700169983721471, 2)
203
+ # {
204
+ # 0 => [617700169983721471],
205
+ # 1 = >[
206
+ # 617700170047946751, 617700169984245759, 617700169982672895,
207
+ # 617700169983983615, 617700170044276735, 617700170044014591
208
+ # ],
209
+ # 2 => [
210
+ # 617700170048995327, 617700170047684607, 617700170048471039,
211
+ # 617700169988177919, 617700169983197183, 617700169983459327,
212
+ # 617700169982935039, 617700175096053759, 617700175097102335,
213
+ # 617700170043752447, 617700170043490303, 617700170045063167
214
+ # ]
215
+ # }
216
+ #
217
+ # @raise [ArgumentError] Raised when the hex range contains a pentagon.
218
+ #
219
+ # @return [Hash] Hex range grouped by distance.
220
+ def hex_range_distances(origin, k)
221
+ max_out_size = max_kring_size(k)
222
+ out = FFI::MemoryPointer.new(H3_INDEX, max_out_size)
223
+ distances = FFI::MemoryPointer.new(:int, max_out_size)
224
+ pentagonal_distortion = Bindings::Private.hex_range_distances(origin, k, out, distances)
225
+ raise(ArgumentError, "Specified hexagon range contains a pentagon") if pentagonal_distortion
226
+
227
+ hexagons = out.read_array_of_ulong_long(max_out_size)
228
+ distances = distances.read_array_of_int(max_out_size)
229
+
230
+ Hash[
231
+ distances.zip(hexagons).group_by(&:first).map { |d, hs| [d, hs.map(&:last)] }
232
+ ]
233
+ end
234
+
235
+ # Derives the k-ring for the given origin at k distance, sub-grouped by distance.
236
+ #
237
+ # @param [Integer] origin Origin H3 index.
238
+ # @param [Integer] k K distance.
239
+ #
240
+ # @example Derive k-ring at distance 2
241
+ # H3.k_ring_distances(617700169983721471, 2)
242
+ # {
243
+ # 0 => [617700169983721471],
244
+ # 1 = >[
245
+ # 617700170047946751, 617700169984245759, 617700169982672895,
246
+ # 617700169983983615, 617700170044276735, 617700170044014591
247
+ # ],
248
+ # 2 => [
249
+ # 617700170048995327, 617700170047684607, 617700170048471039,
250
+ # 617700169988177919, 617700169983197183, 617700169983459327,
251
+ # 617700169982935039, 617700175096053759, 617700175097102335,
252
+ # 617700170043752447, 617700170043490303, 617700170045063167
253
+ # ]
254
+ # }
255
+ #
256
+ # @return [Hash] Hash of k-ring distances grouped by distance.
257
+ def k_ring_distances(origin, k)
258
+ max_out_size = max_kring_size(k)
259
+ out = FFI::MemoryPointer.new(H3_INDEX, max_out_size)
260
+ distances = FFI::MemoryPointer.new(:int, max_out_size)
261
+ Bindings::Private.k_ring_distances(origin, k, out, distances)
262
+
263
+ hexagons = out.read_array_of_ulong_long(max_out_size)
264
+ distances = distances.read_array_of_int(max_out_size)
265
+
266
+ Hash[
267
+ distances.zip(hexagons).group_by(&:first).map { |d, hs| [d, hs.map(&:last)] }
268
+ ]
269
+ end
270
+
271
+ private
272
+
273
+ def hex_ranges_ungrouped(h3_set, k)
274
+ h3_set = h3_set.uniq
275
+ max_out_size = h3_set.size * max_kring_size(k)
276
+ out = FFI::MemoryPointer.new(H3_INDEX, max_out_size)
277
+ pentagonal_distortion = false
278
+ FFI::MemoryPointer.new(H3_INDEX, h3_set.size) do |h3_set_ptr|
279
+ h3_set_ptr.write_array_of_ulong_long(h3_set)
280
+ pentagonal_distortion = Bindings::Private.hex_ranges(h3_set_ptr, h3_set.size, k, out)
281
+ end
282
+ raise(ArgumentError, "One of the specified hexagon ranges contains a pentagon") if pentagonal_distortion
283
+
284
+ out.read_array_of_ulong_long(max_out_size)
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,146 @@
1
+ module H3
2
+ # Unidirectional edge functions
3
+ #
4
+ # @see https://uber.github.io/h3/#/documentation/api-reference/unidirectional-edges
5
+ module UnidirectionalEdges
6
+ extend H3::Bindings::Base
7
+
8
+ # @!method h3_indexes_neighbors?(origin, destination)
9
+ #
10
+ # Determine whether two H3 indexes are neighbors.
11
+ #
12
+ # @param [Integer] origin Origin H3 index
13
+ # @param [Integer] destination Destination H3 index
14
+ #
15
+ # @example Check two H3 indexes
16
+ # H3.h3_indexes_neighbors?(617700169958293503, 617700169958031359)
17
+ # true
18
+ #
19
+ # @return [Boolean] True if indexes are neighbors
20
+ attach_function :h3_indexes_neighbors, :h3IndexesAreNeighbors, [ :h3_index, :h3_index ], :bool
21
+
22
+ # @!method h3_unidirectional_edge_valid?(h3_index)
23
+ #
24
+ # Determine whether the given H3 index represents an edge.
25
+ #
26
+ # @param [Integer] h3_index H3 index
27
+ #
28
+ # @example Check if H3 index is a valid unidirectional edge.
29
+ # H3.h3_unidirectional_edge_valid?(1266218516299644927)
30
+ # true
31
+ #
32
+ # @return [Boolean] True if H3 index is a valid unidirectional edge
33
+ attach_function :h3_unidirectional_edge_valid,
34
+ :h3UnidirectionalEdgeIsValid,
35
+ [ :h3_index ],
36
+ :bool
37
+
38
+ # @!method h3_unidirectional_edge(origin, destination)
39
+ #
40
+ # Derives the H3 index of the edge from the given H3 indexes.
41
+ #
42
+ # @param [Integer] origin H3 index
43
+ # @param [Integer] destination H3 index
44
+ #
45
+ # @example Derive the H3 edge index between two H3 indexes
46
+ # H3.h3_unidirectional_edge(617700169958293503, 617700169958031359)
47
+ # 1626506486489284607
48
+ #
49
+ # @return [Integer] H3 edge index
50
+ attach_function :h3_unidirectional_edge,
51
+ :getH3UnidirectionalEdge,
52
+ [ :h3_index, :h3_index ],
53
+ :h3_index
54
+
55
+ # @!method destination_from_unidirectional_edge(edge)
56
+ #
57
+ # Derive destination H3 index from edge.
58
+ #
59
+ # @param [Integer] edge H3 edge index
60
+ #
61
+ # @example Get destination index from edge
62
+ # H3.destination_from_unidirectional_edge(1266218516299644927)
63
+ # 617700169961177087
64
+ #
65
+ # @return [Integer] H3 index
66
+ attach_function :destination_from_unidirectional_edge,
67
+ :getDestinationH3IndexFromUnidirectionalEdge,
68
+ [ :h3_index ],
69
+ :h3_index
70
+
71
+ # @!method origin_from_unidirectional_edge(edge)
72
+ #
73
+ # Derive origin H3 index from edge.
74
+ #
75
+ # @param [Integer] edge H3 edge index
76
+ #
77
+ # @example Get origin index from edge
78
+ # H3.origin_from_unidirectional_edge(1266218516299644927)
79
+ # 617700169958293503
80
+ #
81
+ # @return [Integer] H3 index
82
+ attach_function :origin_from_unidirectional_edge,
83
+ :getOriginH3IndexFromUnidirectionalEdge,
84
+ [ :h3_index ],
85
+ :h3_index
86
+
87
+ # Derive origin and destination H3 indexes from edge.
88
+ #
89
+ # Returned in the form
90
+ #
91
+ # [origin, destination]
92
+ #
93
+ # @param [Integer] edge H3 edge index
94
+ #
95
+ # @example Get origin and destination indexes from edge
96
+ # H3.h3_indexes_from_unidirectional_edge(1266218516299644927)
97
+ # [617700169958293503, 617700169961177087]
98
+ #
99
+ # @return [Array<Integer>] H3 index array.
100
+ def h3_indexes_from_unidirectional_edge(edge)
101
+ max_hexagons = 2
102
+ origin_destination = FFI::MemoryPointer.new(:ulong_long, max_hexagons)
103
+ Bindings::Private.h3_indexes_from_unidirectional_edge(edge, origin_destination)
104
+ origin_destination.read_array_of_ulong_long(max_hexagons).reject(&:zero?)
105
+ end
106
+
107
+ # Derive unidirectional edges for a H3 index.
108
+ #
109
+ # @param [Integer] origin H3 index
110
+ #
111
+ # @example Get unidirectional indexes from hexagon
112
+ # H3.h3_unidirectional_edges_from_hexagon(1266218516299644927)
113
+ # [
114
+ # 1266218516299644927, 1338276110337572863, 1410333704375500799,
115
+ # 1482391298413428735, 1554448892451356671, 1626506486489284607
116
+ # ]
117
+ #
118
+ # @return [Array<Integer>] H3 index array.
119
+ def h3_unidirectional_edges_from_hexagon(origin)
120
+ max_edges = 6
121
+ edges = FFI::MemoryPointer.new(:ulong_long, max_edges)
122
+ Bindings::Private.h3_unidirectional_edges_from_hexagon(origin, edges)
123
+ edges.read_array_of_ulong_long(max_edges).reject(&:zero?)
124
+ end
125
+
126
+ # Derive coordinates for edge boundary.
127
+ #
128
+ # @param [Integer] edge H3 edge index
129
+ #
130
+ # @example
131
+ # H3.h3_unidirectional_edge_boundary(1266218516299644927)
132
+ # [
133
+ # [37.77820687262237, -122.41971895414808],
134
+ # [37.77652420699321, -122.42079024541876]
135
+ # ]
136
+ #
137
+ # @return [Array<Array<Float>>] Edge boundary coordinates for a hexagon
138
+ def h3_unidirectional_edge_boundary(edge)
139
+ geo_boundary = Bindings::Structs::GeoBoundary.new
140
+ Bindings::Private.h3_unidirectional_edge_boundary(edge, geo_boundary)
141
+ geo_boundary[:verts].take(geo_boundary[:num_verts] * 2).map do |d|
142
+ rads_to_degs(d)
143
+ end.each_slice(2).to_a
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,3 @@
1
+ module H3
2
+ VERSION = "3.2.0"
3
+ end
@@ -0,0 +1,118 @@
1
+ RSpec.describe H3 do
2
+ # http://geojson.io is helpful for setting up test fixtures
3
+ describe ".geo_json_to_coordinates" do
4
+ let(:input) do
5
+ File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json"))
6
+ end
7
+ let(:coordinates) do
8
+ [
9
+ [
10
+ [52.24630137198303, -1.7358398437499998],
11
+ [52.05249047600099, -1.8923950195312498],
12
+ [51.891749018068246, -1.56829833984375],
13
+ [51.91208502557545, -1.27716064453125],
14
+ [52.032218104145294, -1.19476318359375],
15
+ [52.19413974159753, -1.24420166015625],
16
+ [52.24125614966341, -1.5902709960937498],
17
+ [52.24630137198303, -1.7358398437499998]
18
+ ],
19
+ [
20
+ [52.12590076522272, -1.58203125],
21
+ [52.12590076522272, -1.476287841796875],
22
+ [52.075285904832334, -1.46392822265625],
23
+ [52.06937709602395, -1.58203125],
24
+ [52.12590076522272, -1.58203125]
25
+ ],
26
+ [
27
+ [52.01531743663362, -1.4556884765625],
28
+ [51.97642166216334, -1.483154296875],
29
+ [51.96626938051444, -1.3677978515625],
30
+ [52.0102459910103, -1.3568115234375],
31
+ [52.01531743663362, -1.4556884765625]
32
+ ]
33
+ ]
34
+ end
35
+
36
+ subject(:geo_json_to_coordinates) { H3.geo_json_to_coordinates(input) }
37
+
38
+ it { is_expected.to eq coordinates }
39
+
40
+ context "when given a feature" do
41
+ let(:input) do
42
+ File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury_feature.json"))
43
+ end
44
+
45
+ it { is_expected.to eq coordinates }
46
+ end
47
+
48
+ context "when given a feature collection" do
49
+ let(:input) do
50
+ File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury_feature_collection.json"))
51
+ end
52
+
53
+ it { is_expected.to eq coordinates }
54
+ end
55
+
56
+ context "when given bad input" do
57
+ let(:input) { "blah" }
58
+
59
+ it "raises an error" do
60
+ expect { geo_json_to_coordinates }.to raise_error(ArgumentError)
61
+ end
62
+ end
63
+ end
64
+
65
+ describe ".coordinates_to_geo_json" do
66
+ let(:input) do
67
+ [
68
+ [
69
+ [52.24630137198303, -1.7358398437499998],
70
+ [52.05249047600099, -1.8923950195312498],
71
+ [51.891749018068246, -1.56829833984375],
72
+ [51.91208502557545, -1.27716064453125],
73
+ [52.032218104145294, -1.19476318359375],
74
+ [52.19413974159753, -1.24420166015625],
75
+ [52.24125614966341, -1.5902709960937498],
76
+ [52.24630137198303, -1.7358398437499998]
77
+ ],
78
+ [
79
+ [52.12590076522272, -1.58203125],
80
+ [52.12590076522272, -1.476287841796875],
81
+ [52.075285904832334, -1.46392822265625],
82
+ [52.06937709602395, -1.58203125],
83
+ [52.12590076522272, -1.58203125]
84
+ ],
85
+ [
86
+ [52.01531743663362, -1.4556884765625],
87
+ [51.97642166216334, -1.483154296875],
88
+ [51.96626938051444, -1.3677978515625],
89
+ [52.0102459910103, -1.3568115234375],
90
+ [52.01531743663362, -1.4556884765625]
91
+ ]
92
+ ]
93
+ end
94
+ let(:geojson) do
95
+ File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json"))
96
+ end
97
+
98
+ subject(:coordinates_to_geo_json) { H3.coordinates_to_geo_json(input) }
99
+
100
+ it { is_expected.to eq geojson }
101
+
102
+ context "when given bad input" do
103
+ let(:input) { "blah" }
104
+
105
+ it "raises an error" do
106
+ expect { coordinates_to_geo_json }.to raise_error(ArgumentError)
107
+ end
108
+ end
109
+
110
+ context "when given nested bad input" do
111
+ let(:input) { [[],[[[[]]]]] }
112
+
113
+ it "raises an error" do
114
+ expect { coordinates_to_geo_json }.to raise_error(ArgumentError)
115
+ end
116
+ end
117
+ end
118
+ end