h3 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ module H3
2
+ # Indexing functions.
3
+ #
4
+ # Coordinates are returned in degrees, in the form
5
+ #
6
+ # [latitude, longitude]
7
+ #
8
+ # @see https://uber.github.io/h3/#/documentation/api-reference/indexing
9
+ module Indexing
10
+ # Derive H3 index for the given set of coordinates.
11
+ #
12
+ # @param [Array<Integer>] coords A coordinate pair.
13
+ # @param [Integer] resolution The desired resolution of the H3 index.
14
+ #
15
+ # @example Derive the H3 index for the given coordinates.
16
+ # H3.geo_to_h3([52.24630137198303, -1.7358398437499998], 9)
17
+ # 617439284584775679
18
+ #
19
+ # @raise [ArgumentError] If coordinates are invalid.
20
+ #
21
+ # @return [Integer] H3 index.
22
+ def geo_to_h3(coords, resolution)
23
+ raise ArgumentError unless coords.is_a?(Array) && coords.count == 2
24
+
25
+ lat, lon = coords
26
+
27
+ if lat > 90 || lat < -90 || lon > 180 || lon < -180
28
+ raise(ArgumentError, "Invalid coordinates")
29
+ end
30
+
31
+ coords = Bindings::Structs::GeoCoord.new
32
+ coords[:lat] = degs_to_rads(lat)
33
+ coords[:lon] = degs_to_rads(lon)
34
+ Bindings::Private.geo_to_h3(coords, resolution)
35
+ end
36
+
37
+ # Derive coordinates for a given H3 index.
38
+ #
39
+ # The coordinates map to the centre of the hexagon at the given index.
40
+ #
41
+ # @param [Integer] h3_index A valid H3 index.
42
+ #
43
+ # @example Derive the central coordinates for the given H3 index.
44
+ # H3.h3_to_geo(617439284584775679)
45
+ # [52.245519061399506, -1.7363137757391423]
46
+ #
47
+ # @return [Array<Integer>] A coordinate pair.
48
+ def h3_to_geo(h3_index)
49
+ coords = Bindings::Structs::GeoCoord.new
50
+ Bindings::Private.h3_to_geo(h3_index, coords)
51
+ [rads_to_degs(coords[:lat]), rads_to_degs(coords[:lon])]
52
+ end
53
+
54
+ # Derive the geographical boundary as coordinates for a given H3 index.
55
+ #
56
+ # This will be a set of 6 coordinate pairs matching the vertexes of the
57
+ # hexagon represented by the given H3 index.
58
+ #
59
+ # If the H3 index is a pentagon, there will be only 5 coordinate pairs returned.
60
+ #
61
+ # @param [Integer] h3_index A valid H3 index.
62
+ #
63
+ # @example Derive the geographical boundary for the given H3 index.
64
+ # H3.h3_to_geo_boundary(617439284584775679)
65
+ # [
66
+ # [52.247260929171055, -1.736809158397472], [52.24625850761068, -1.7389279144996015],
67
+ # [52.244516619273476, -1.7384324668792375], [52.243777169245725, -1.7358184256304658],
68
+ # [52.24477956752282, -1.7336997597088104], [52.246521439109415, -1.7341950448552204]
69
+ # ]
70
+ #
71
+ # @return [Array<Array<Integer>>] An array of six coordinate pairs.
72
+ def h3_to_geo_boundary(h3_index)
73
+ geo_boundary = Bindings::Structs::GeoBoundary.new
74
+ Bindings::Private.h3_to_geo_boundary(h3_index, geo_boundary)
75
+ geo_boundary[:verts].take(geo_boundary[:num_verts] * 2).map do |d|
76
+ rads_to_degs(d)
77
+ end.each_slice(2).to_a
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,105 @@
1
+ module H3
2
+ # Index inspection functions.
3
+ #
4
+ # @see https://uber.github.io/h3/#/documentation/api-reference/inspection
5
+ module Inspection
6
+ extend H3::Bindings::Base
7
+
8
+ H3_TO_STR_BUF_SIZE = 32
9
+ private_constant :H3_TO_STR_BUF_SIZE
10
+
11
+ # @!method h3_resolution(h3_index)
12
+ #
13
+ # Derive the resolution of a given H3 index
14
+ #
15
+ # @param [Integer] h3_index A valid H3 index
16
+ #
17
+ # @example Derive the resolution of a H3 index
18
+ # H3.h3_resolution(617700440100569087)
19
+ # 9
20
+ #
21
+ # @return [Integer] Resolution of H3 index
22
+ attach_function :h3_resolution, :h3GetResolution, [ :h3_index ], :int
23
+
24
+ # @!method h3_base_cell(h3_index)
25
+ #
26
+ # Derives the base cell number of the given H3 index
27
+ #
28
+ # @param [Integer] h3_index A valid H3 index
29
+ #
30
+ # @example Derive the base cell number of a H3 index
31
+ # H3.h3_base_cell(617700440100569087)
32
+ # 20
33
+ #
34
+ # @return [Integer] Base cell number
35
+ attach_function :h3_base_cell, :h3GetBaseCell, [ :h3_index ], :int
36
+
37
+ # @!method string_to_h3(h3_string)
38
+ #
39
+ # Derives the H3 index for a given hexadecimal string representation.
40
+ #
41
+ # @param [String] h3_string A H3 index in hexadecimal form.
42
+ #
43
+ # @example Derive the H3 index from the given hexadecimal form.
44
+ # H3.string_to_h3("8928308280fffff")
45
+ # 617700169958293503
46
+ #
47
+ # @return [Integer] H3 index
48
+ attach_function :string_to_h3, :stringToH3, [ :string ], :h3_index
49
+
50
+ # @!method h3_pentagon?(h3_index)
51
+ #
52
+ # Determine whether the given H3 index is a pentagon.
53
+ #
54
+ # @param [Integer] h3_index A valid H3 index.
55
+ #
56
+ # @example Check if H3 index is a pentagon
57
+ # H3.h3_pentagon?(585961082523222015)
58
+ # true
59
+ #
60
+ # @return [Boolean] True if the H3 index is a pentagon.
61
+ attach_function :h3_pentagon, :h3IsPentagon, [ :h3_index ], :bool
62
+
63
+ # @!method h3_res_class_3?(h3_index)
64
+ #
65
+ # Determine whether the given H3 index has a resolution with
66
+ # Class III orientation.
67
+ #
68
+ # @param [Integer] h3_index A valid H3 index.
69
+ #
70
+ # @example Check if H3 index has a class III resolution.
71
+ # H3.h3_res_class_3?(599686042433355775)
72
+ # true
73
+ #
74
+ # @return [Boolean] True if the H3 index has a class III resolution.
75
+ attach_function :h3_res_class_3, :h3IsResClassIII, [ :h3_index ], :bool
76
+
77
+ # @!method h3_valid?(h3_index)
78
+ #
79
+ # Determine whether the given H3 index is valid.
80
+ #
81
+ # @param [Integer] h3_index A H3 index.
82
+ #
83
+ # @example Check if H3 index is valid
84
+ # H3.h3_valid?(599686042433355775)
85
+ # true
86
+ #
87
+ # @return [Boolean] True if the H3 index is valid.
88
+ attach_function :h3_valid, :h3IsValid, [ :h3_index ], :bool
89
+
90
+ # Derives the hexadecimal string representation for a given H3 index.
91
+ #
92
+ # @param [Integer] h3_index A valid H3 index.
93
+ #
94
+ # @example Derive the given hexadecimal form for the H3 index
95
+ # H3.h3_to_string(617700169958293503)
96
+ # "89283470dcbffff"
97
+ #
98
+ # @return [String] H3 index in hexadecimal form.
99
+ def h3_to_string(h3_index)
100
+ h3_str = FFI::MemoryPointer.new(:char, H3_TO_STR_BUF_SIZE)
101
+ Bindings::Private.h3_to_string(h3_index, h3_str, H3_TO_STR_BUF_SIZE)
102
+ h3_str.read_string
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,99 @@
1
+ module H3
2
+ # Miscellaneous functions.
3
+ #
4
+ # @see https://uber.github.io/h3/#/documentation/api-reference/miscellaneous
5
+ module Miscellaneous
6
+ extend H3::Bindings::Base
7
+
8
+ # @!method degs_to_rads(degs)
9
+ #
10
+ # Convert a number expressed in degrees to its equivalent in radians.
11
+ #
12
+ # @param [Float] degs Value expressed in degrees.
13
+ #
14
+ # @example Convert degrees value to radians.
15
+ # H3.degs_to_rads(19.61922082086965)
16
+ # 0.34242
17
+ #
18
+ # @return [Float] Value expressed in radians.
19
+ attach_function :degs_to_rads, :degsToRads, [ :double ], :double
20
+
21
+ # @!method edge_length_km(resolution)
22
+ #
23
+ # Derive the length of a hexagon edge in kilometres at the given resolution.
24
+ #
25
+ # @param [Integer] resolution Resolution.
26
+ #
27
+ # @example Derive length of edge in kilometres
28
+ # H3.edge_length_km(3)
29
+ # 59.81085794
30
+ #
31
+ # @return [Float] Length of edge in kilometres
32
+ attach_function :edge_length_km, :edgeLengthKm, [ :int ], :double
33
+
34
+ # @!method edge_length_m(resolution)
35
+ #
36
+ # Derive the length of a hexagon edge in metres at the given resolution.
37
+ #
38
+ # @param [Integer] resolution Resolution.
39
+ #
40
+ # @example Derive length of edge in metres
41
+ # H3.edge_length_m(6)
42
+ # 3229.482772
43
+ #
44
+ # @return [Float] Length of edge in metres
45
+ attach_function :edge_length_m, :edgeLengthM, [ :int ], :double
46
+
47
+ # @!method hex_area_km2(resolution)
48
+ #
49
+ # Average hexagon area in square kilometres at the given resolution.
50
+ #
51
+ # @param [Integer] resolution Resolution.
52
+ #
53
+ # @example Find the square kilometre size at resolution 5
54
+ # H3.hex_area_km2(5)
55
+ # 252.9033645
56
+ #
57
+ # @return [Float] Average hexagon area in square kilometres.
58
+ attach_function :hex_area_km2, :hexAreaKm2, [ :int ], :double
59
+
60
+ # @!method hex_area_m2(resolution)
61
+ #
62
+ # Average hexagon area in square metres at the given resolution.
63
+ #
64
+ # @param [Integer] resolution Resolution.
65
+ #
66
+ # @example Find the square metre size at resolution 10
67
+ # H3.hex_area_m2(10)
68
+ # 15047.5
69
+ #
70
+ # @return [Float] Average hexagon area in square metres.
71
+ attach_function :hex_area_m2, :hexAreaM2, [ :int ], :double
72
+
73
+ # @!method num_hexagons(resolution)
74
+ #
75
+ # Number of unique H3 indexes at the given resolution.
76
+ #
77
+ # @param [Integer] resolution Resolution.
78
+ #
79
+ # @example Find number of hexagons at resolution 6
80
+ # H3.num_hexagons(6)
81
+ # 14117882
82
+ #
83
+ # @return [Integer] Number of unique hexagons
84
+ attach_function :num_hexagons, :numHexagons, [ :int ], :ulong_long
85
+
86
+ # @!method rads_to_degs(rads)
87
+ #
88
+ # Convert a number expressed in radians to its equivalent in degrees.
89
+ #
90
+ # @param [Float] rads Value expressed in radians.
91
+ #
92
+ # @example Convert radians value to degrees.
93
+ # H3.rads_to_degs(0.34242)
94
+ # 19.61922082086965
95
+ #
96
+ # @return [Float] Value expressed in degrees.
97
+ attach_function :rads_to_degs, :radsToDegs, [ :double ], :double
98
+ end
99
+ end
@@ -0,0 +1,239 @@
1
+ module H3
2
+ # Region functions.
3
+ #
4
+ # @see https://uber.github.io/h3/#/documentation/api-reference/regions
5
+ module Regions
6
+ extend H3::Bindings::Base
7
+
8
+ # Derive the maximum number of H3 indexes that could be returned from the input.
9
+ #
10
+ # @param [String, Array<Array<Array<Float>>>] geo_polygon Either a GeoJSON string or a coordinates nested array.
11
+ # @param [Integer] resolution Resolution.
12
+ #
13
+ # @example Derive maximum number of hexagons for given GeoJSON document.
14
+ # geo_json = "{\"type\":\"Polygon\",\"coordinates\":[[[-1.7358398437499998,52.24630137198303],
15
+ # [-1.8923950195312498,52.05249047600099],[-1.56829833984375,51.891749018068246],
16
+ # [-1.27716064453125,51.91208502557545],[-1.19476318359375,52.032218104145294],
17
+ # [-1.24420166015625,52.19413974159753],[-1.5902709960937498,52.24125614966341],
18
+ # [-1.7358398437499998,52.24630137198303]],[[-1.58203125,52.12590076522272],
19
+ # [-1.476287841796875,52.12590076522272],[-1.46392822265625,52.075285904832334],
20
+ # [-1.58203125,52.06937709602395],[-1.58203125,52.12590076522272]],
21
+ # [[-1.4556884765625,52.01531743663362],[-1.483154296875,51.97642166216334],
22
+ # [-1.3677978515625,51.96626938051444],[-1.3568115234375,52.0102459910103],
23
+ # [-1.4556884765625,52.01531743663362]]]}"
24
+ # H3.max_polyfill_size(geo_json, 9)
25
+ # 33391
26
+ #
27
+ # @example Derive maximum number of hexagons for a nested array of coordinates.
28
+ # coordinates = [
29
+ # [
30
+ # [52.24630137198303, -1.7358398437499998], [52.05249047600099, -1.8923950195312498],
31
+ # [51.891749018068246, -1.56829833984375], [51.91208502557545, -1.27716064453125],
32
+ # [52.032218104145294, -1.19476318359375], [52.19413974159753, -1.24420166015625],
33
+ # [52.24125614966341, -1.5902709960937498], [52.24630137198303, -1.7358398437499998]
34
+ # ],
35
+ # [
36
+ # [52.12590076522272, -1.58203125], [52.12590076522272, -1.476287841796875],
37
+ # [52.075285904832334, -1.46392822265625], [52.06937709602395, -1.58203125],
38
+ # [52.12590076522272, -1.58203125]
39
+ # ],
40
+ # [
41
+ # [52.01531743663362, -1.4556884765625], [51.97642166216334, -1.483154296875],
42
+ # [51.96626938051444, -1.3677978515625], [52.0102459910103, -1.3568115234375],
43
+ # [52.01531743663362, -1.4556884765625]
44
+ # ]
45
+ # ]
46
+ # H3.max_polyfill_size(coordinates, 9)
47
+ # 33391
48
+ #
49
+ # @return [Integer] Maximum number of hexagons needed to polyfill given area.
50
+ def max_polyfill_size(geo_polygon, resolution)
51
+ geo_polygon = geo_json_to_coordinates(geo_polygon) if geo_polygon.is_a?(String)
52
+ Bindings::Private.max_polyfill_size(build_polygon(geo_polygon), resolution)
53
+ end
54
+
55
+ # Derive a list of H3 indexes that fall within a given geo polygon structure.
56
+ #
57
+ # @param [String, Array<Array<Array<Float>>>] geo_polygon Either a GeoJSON string or a coordinates nested array.
58
+ # @param [Integer] resolution Resolution.
59
+ #
60
+ # @example Derive hexagons for given GeoJSON document.
61
+ # geo_json = "{\"type\":\"Polygon\",\"coordinates\":[[[-1.7358398437499998,52.24630137198303],
62
+ # [-1.8923950195312498,52.05249047600099],[-1.56829833984375,51.891749018068246],
63
+ # [-1.27716064453125,51.91208502557545],[-1.19476318359375,52.032218104145294],
64
+ # [-1.24420166015625,52.19413974159753],[-1.5902709960937498,52.24125614966341],
65
+ # [-1.7358398437499998,52.24630137198303]],[[-1.58203125,52.12590076522272],
66
+ # [-1.476287841796875,52.12590076522272],[-1.46392822265625,52.075285904832334],
67
+ # [-1.58203125,52.06937709602395],[-1.58203125,52.12590076522272]],
68
+ # [[-1.4556884765625,52.01531743663362],[-1.483154296875,51.97642166216334],
69
+ # [-1.3677978515625,51.96626938051444],[-1.3568115234375,52.0102459910103],
70
+ # [-1.4556884765625,52.01531743663362]]]}"
71
+ # H3.polyfill(geo_json, 5)
72
+ # [
73
+ # 599424968551301119, 599424888020664319, 599424970698784767, 599424964256333823,
74
+ # 599424969625042943, 599425001837297663, 599425000763555839
75
+ # ]
76
+ #
77
+ # @example Derive hexagons for a nested array of coordinates.
78
+ # coordinates = [
79
+ # [
80
+ # [52.24630137198303, -1.7358398437499998], [52.05249047600099, -1.8923950195312498],
81
+ # [51.891749018068246, -1.56829833984375], [51.91208502557545, -1.27716064453125],
82
+ # [52.032218104145294, -1.19476318359375], [52.19413974159753, -1.24420166015625],
83
+ # [52.24125614966341, -1.5902709960937498], [52.24630137198303, -1.7358398437499998]
84
+ # ],
85
+ # [
86
+ # [52.12590076522272, -1.58203125], [52.12590076522272, -1.476287841796875],
87
+ # [52.075285904832334, -1.46392822265625], [52.06937709602395, -1.58203125],
88
+ # [52.12590076522272, -1.58203125]
89
+ # ],
90
+ # [
91
+ # [52.01531743663362, -1.4556884765625], [51.97642166216334, -1.483154296875],
92
+ # [51.96626938051444, -1.3677978515625], [52.0102459910103, -1.3568115234375],
93
+ # [52.01531743663362, -1.4556884765625]
94
+ # ]
95
+ # ]
96
+ # H3.polyfill(coordinates, 5)
97
+ # [
98
+ # 599424968551301119, 599424888020664319, 599424970698784767, 599424964256333823,
99
+ # 599424969625042943, 599425001837297663, 599425000763555839
100
+ # ]
101
+ #
102
+ # @return [Array<Integer>] Hexagons needed to polyfill given area.
103
+ def polyfill(geo_polygon, resolution)
104
+ geo_polygon = geo_json_to_coordinates(geo_polygon) if geo_polygon.is_a?(String)
105
+ max_size = max_polyfill_size(geo_polygon, resolution)
106
+ out = FFI::MemoryPointer.new(H3_INDEX, max_size)
107
+ Bindings::Private.polyfill(build_polygon(geo_polygon), resolution, out)
108
+ out.read_array_of_ulong_long(max_size).reject(&:zero?)
109
+ end
110
+
111
+ # Derive a nested array of coordinates from a list of H3 indexes.
112
+ #
113
+ # @param [Array<Integer>] h3_indexes A list of H3 indexes.
114
+ #
115
+ # @example Get a set of coordinates from a given list of H3 indexes.
116
+ # h3_indexes = [
117
+ # 599424968551301119, 599424888020664319, 599424970698784767,
118
+ # 599424964256333823, 599424969625042943, 599425001837297663,
119
+ # 599425000763555839
120
+ # ]
121
+ # H3.h3_set_to_linked_geo(h3_indexes)
122
+ # [
123
+ # [
124
+ # [52.24425364171531, -1.6470570189756442], [52.19515282473624, -1.7508281227260887],
125
+ # [52.10973325363767, -1.7265910686763437], [52.06042870859474, -1.8301115887419024],
126
+ # [51.97490199314513, -1.8057974545517919], [51.9387204737266, -1.6783497689296265],
127
+ # [51.853128001893175, -1.654344796003053], [51.81682604752331, -1.5274195136674955],
128
+ # [51.866019925789956, -1.424329996292339], [51.829502535462176, -1.2977583914075301],
129
+ # [51.87843896218677, -1.1946402363628545], [51.96394676922824, -1.21787542551618],
130
+ # [52.01267958543637, -1.1145114691876956], [52.09808058649905, -1.1376655003242908],
131
+ # [52.134791926560325, -1.26456988729442], [52.22012854584846, -1.2880298658365215],
132
+ # [52.25672060485973, -1.4154623025177386], [52.20787927927604, -1.5192658757247421]
133
+ # ]
134
+ # ]
135
+ #
136
+ # @return [Array<Array<Array<Float>>>] Nested array of coordinates.
137
+ def h3_set_to_linked_geo(h3_indexes)
138
+ h3_indexes.uniq!
139
+ linked_geo_polygon = Bindings::Structs::LinkedGeoPolygon.new
140
+ FFI::MemoryPointer.new(H3_INDEX, h3_indexes.size) do |hexagons_ptr|
141
+ hexagons_ptr.write_array_of_ulong_long(h3_indexes)
142
+ Bindings::Private.h3_set_to_linked_geo(hexagons_ptr, h3_indexes.size, linked_geo_polygon)
143
+ end
144
+
145
+ extract_linked_geo_polygon(linked_geo_polygon).first
146
+ end
147
+
148
+ private
149
+
150
+ def extract_linked_geo_polygon(linked_geo_polygon)
151
+ return if linked_geo_polygon.null?
152
+
153
+ geo_polygons = [linked_geo_polygon]
154
+
155
+ until linked_geo_polygon[:next].null? do
156
+ geo_polygons << linked_geo_polygon[:next]
157
+ linked_geo_polygon = linked_geo_polygon[:next]
158
+ end
159
+
160
+ geo_polygons.map(&method(:extract_geo_polygon))
161
+ end
162
+
163
+ def extract_geo_polygon(geo_polygon)
164
+ extract_linked_geo_loop(geo_polygon[:first]) unless geo_polygon[:first].null?
165
+ end
166
+
167
+ def extract_linked_geo_loop(linked_geo_loop)
168
+ return if linked_geo_loop.null?
169
+
170
+ geo_loops = [linked_geo_loop]
171
+
172
+ until linked_geo_loop[:next].null? do
173
+ geo_loops << linked_geo_loop[:next]
174
+ linked_geo_loop = linked_geo_loop[:next]
175
+ end
176
+
177
+ geo_loops.map(&method(:extract_geo_loop))
178
+ end
179
+
180
+ def extract_geo_loop(geo_loop)
181
+ extract_linked_geo_coord(geo_loop[:first]) unless geo_loop[:first].null?
182
+ end
183
+
184
+ def extract_linked_geo_coord(linked_geo_coord)
185
+ return if linked_geo_coord.null?
186
+
187
+ geo_coords = [linked_geo_coord]
188
+
189
+ until linked_geo_coord[:next].null? do
190
+ geo_coords << linked_geo_coord[:next]
191
+ linked_geo_coord = linked_geo_coord[:next]
192
+ end
193
+
194
+ geo_coords.map(&method(:extract_geo_coord))
195
+ end
196
+
197
+ def extract_geo_coord(geo_coord)
198
+ [
199
+ rads_to_degs(geo_coord[:vertex][:lat]),
200
+ rads_to_degs(geo_coord[:vertex][:lon])
201
+ ]
202
+ end
203
+
204
+ def build_polygon(input)
205
+ outline, *holes = input
206
+ geo_polygon = Bindings::Structs::GeoPolygon.new
207
+ geo_polygon[:geofence] = build_geofence(outline)
208
+ len = holes.count
209
+ geo_polygon[:num_holes] = len
210
+ geofences = holes.map(&method(:build_geofence))
211
+ ptr = FFI::MemoryPointer.new(Bindings::Structs::GeoFence, len)
212
+ fence_structs = geofences.count.times.map do |i|
213
+ Bindings::Structs::GeoFence.new(ptr + i * Bindings::Structs::GeoFence.size())
214
+ end
215
+ geofences.each_with_index do |geofence, i|
216
+ fence_structs[i][:num_verts] = geofence[:num_verts]
217
+ fence_structs[i][:verts] = geofence[:verts]
218
+ end
219
+ geo_polygon[:holes] = ptr
220
+ geo_polygon
221
+ end
222
+
223
+ def build_geofence(input)
224
+ geo_fence = Bindings::Structs::GeoFence.new
225
+ len = input.count
226
+ geo_fence[:num_verts] = len
227
+ ptr = FFI::MemoryPointer.new(Bindings::Structs::GeoCoord, len)
228
+ coords = 0.upto(len).map do |i|
229
+ Bindings::Structs::GeoCoord.new(ptr + i * Bindings::Structs::GeoCoord.size)
230
+ end
231
+ input.each_with_index do |(lat, lon), i|
232
+ coords[i][:lat] = degs_to_rads(lat)
233
+ coords[i][:lon] = degs_to_rads(lon)
234
+ end
235
+ geo_fence[:verts] = ptr
236
+ geo_fence
237
+ end
238
+ end
239
+ end