h3 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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