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,36 @@
1
+ require "ffi"
2
+ require "rgeo/geo_json"
3
+
4
+ require "h3/bindings"
5
+ require "h3/geo_json"
6
+ require "h3/hierarchy"
7
+ require "h3/indexing"
8
+ require "h3/inspection"
9
+ require "h3/miscellaneous"
10
+ require "h3/regions"
11
+ require "h3/traversal"
12
+ require "h3/unidirectional_edges"
13
+
14
+ # The main H3 namespace.
15
+ #
16
+ # All public methods for the library are defined here.
17
+ #
18
+ # @see https://uber.github.io/h3/#/documentation/overview/introduction
19
+ module H3
20
+ class << self
21
+ include GeoJSON
22
+ include Hierarchy
23
+ include Miscellaneous
24
+ include Indexing
25
+ include Inspection
26
+ include Regions
27
+ include Traversal
28
+ include UnidirectionalEdges
29
+ end
30
+
31
+ PREDICATES = %i(h3_indexes_neighbors h3_pentagon h3_res_class_3
32
+ h3_unidirectional_edge_valid h3_valid).freeze
33
+ PREDICATES.each do |predicate|
34
+ singleton_class.send(:alias_method, "#{predicate}?".to_sym, predicate)
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ require "h3/bindings/base"
2
+ require "h3/bindings/structs"
3
+ require "h3/bindings/private"
4
+
5
+ module H3
6
+ # Internal bindings related modules and classes.
7
+ #
8
+ # These are intended to be used by the library's public methods
9
+ # and not to be used directly by client code.
10
+ module Bindings
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module H3
2
+ module Bindings
3
+ # Base for FFI Bindings.
4
+ #
5
+ # When extended, this module sets up FFI to use the H3 C library.
6
+ module Base
7
+ def self.extended(base)
8
+ base.extend FFI::Library
9
+ base.ffi_lib ["libh3", "libh3.1"]
10
+ base.typedef :ulong_long, :h3_index
11
+ base.const_set('H3_INDEX', :ulong_long)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module H3
2
+ module Bindings
3
+ # Private H3 functions which should not be called directly.
4
+ #
5
+ # This module provides bindings that do not have to be invoked directly by clients
6
+ # of the library. They are used only internally to provide related public interface.
7
+ module Private
8
+ extend H3::Bindings::Base
9
+
10
+ attach_function :compact, [:pointer, :pointer, :int], :bool
11
+ attach_function :geo_to_h3, :geoToH3, [ Bindings::Structs::GeoCoord.by_ref, :int ], :h3_index
12
+ attach_function :h3_indexes_from_unidirectional_edge, :getH3IndexesFromUnidirectionalEdge, [ :h3_index, :pointer ], :void
13
+ attach_function :h3_unidirectional_edges_from_hexagon, :getH3UnidirectionalEdgesFromHexagon, [ :h3_index, :pointer ], :void
14
+ attach_function :h3_set_to_linked_geo, :h3SetToLinkedGeo, [ :pointer, :int, Bindings::Structs::LinkedGeoPolygon.by_ref ], :void
15
+ attach_function :h3_to_children, :h3ToChildren, [ :h3_index, :int, :pointer ], :void
16
+ attach_function :h3_to_geo, :h3ToGeo, [ :h3_index, Bindings::Structs::GeoCoord.by_ref ], :void
17
+ attach_function :h3_to_string, :h3ToString, [ :h3_index, :pointer, :int], :void
18
+ attach_function :h3_to_geo_boundary, :h3ToGeoBoundary, [:h3_index, Bindings::Structs::GeoBoundary.by_ref ], :void
19
+ attach_function :h3_unidirectional_edge_boundary, :getH3UnidirectionalEdgeBoundary, [:h3_index, :pointer], :void
20
+ attach_function :hex_range, :hexRange, [ :h3_index, :int, :pointer ], :bool
21
+ attach_function :hex_range_distances, :hexRangeDistances, [:h3_index, :int, :pointer, :pointer], :bool
22
+ attach_function :hex_ranges, :hexRanges, [ :pointer, :int, :int, :pointer ], :bool
23
+ attach_function :hex_ring, :hexRing, [:h3_index, :int, :pointer], :bool
24
+ attach_function :k_ring, :kRing, [:h3_index, :int, :pointer], :void
25
+ attach_function :k_ring_distances, :kRingDistances, [:h3_index, :int, :pointer, :pointer], :bool
26
+ attach_function :max_polyfill_size, :maxPolyfillSize, [Bindings::Structs::GeoPolygon.by_ref, :int], :int
27
+ attach_function :max_uncompact_size, :maxUncompactSize, [:pointer, :int, :int], :int
28
+ attach_function :polyfill, [ :pointer, :int, :pointer ], :void
29
+ attach_function :uncompact, [:pointer, :int, :pointer, :int, :int], :bool
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,59 @@
1
+ module H3
2
+ module Bindings
3
+ # FFI Structs.
4
+ #
5
+ # These match the structs defined in H3's header file and are required
6
+ # to correctly interact with the library's functions.
7
+ module Structs
8
+ extend FFI::Library
9
+
10
+ class GeoCoord < FFI::Struct
11
+ layout :lat, :double,
12
+ :lon, :double
13
+ end
14
+
15
+ class GeoBoundary < FFI::Struct
16
+ layout :num_verts, :int,
17
+ :verts, [:double, 20] # array of GeoCoord structs (must be fixed length)
18
+ end
19
+
20
+ class GeoFence < FFI::Struct
21
+ layout :num_verts, :int,
22
+ :verts, :pointer # array of GeoCoord structs
23
+ end
24
+
25
+ class GeoPolygon < FFI::Struct
26
+ layout :geofence, GeoFence,
27
+ :num_holes, :int,
28
+ :holes, :pointer # array of GeoFence structs
29
+ end
30
+
31
+ class GeoMultiPolygon < FFI::Struct
32
+ layout :num_polygons, :int,
33
+ :polygons, :pointer # array of GeoPolygon structs
34
+ end
35
+
36
+ class LinkedGeoCoord < FFI::Struct
37
+ layout :vertex, GeoCoord,
38
+ :next, LinkedGeoCoord.ptr
39
+ end
40
+
41
+ class LinkedGeoLoop < FFI::Struct
42
+ layout :first, LinkedGeoCoord.ptr,
43
+ :last, LinkedGeoCoord.ptr,
44
+ :next, LinkedGeoLoop.ptr
45
+ end
46
+
47
+ class LinkedGeoPolygon < FFI::Struct
48
+ layout :first, LinkedGeoLoop.ptr,
49
+ :last, LinkedGeoLoop.ptr,
50
+ :next, LinkedGeoPolygon.ptr
51
+ end
52
+
53
+ class CoordIJ < FFI::Struct
54
+ layout :i, :int,
55
+ :j, :int
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,169 @@
1
+ module H3
2
+ # GeoJSON helper methods.
3
+ #
4
+ # This module allows conversions between GeoJSON polygon data and a nested set of coordinates.
5
+ #
6
+ # It should be noted that H3 describes coordinates as number pairs in the form
7
+ #
8
+ # [latitude, longitude]
9
+ #
10
+ # whereas the GeoJSON standard uses
11
+ #
12
+ # [longitude, latitude]
13
+ #
14
+ # Both use degrees.
15
+ #
16
+ # == Coordinates Array
17
+ #
18
+ # We use a nested array to hold coordinates describing a geographical region.
19
+ #
20
+ # The first element in the array is an external geofence boundary, composed of an array of
21
+ # coordinates as 2-element arrays of the form [latitude, longitude].
22
+ #
23
+ # Any further elements in the array are further geofence arrays of coordinates which describe
24
+ # holes that may be present in the polygon.
25
+ #
26
+ # Specific examples are shown in the individual method details.
27
+ #
28
+ # @see http://geojson.io geojson.io - A graphical tool to see GeoJSON data rendered on a world map.
29
+ # @see https://tools.ietf.org/html/rfc7946 The GeoJSON RFC standard.
30
+ module GeoJSON
31
+ # Convert a GeoJSON document to a nested array of coordinates.
32
+ #
33
+ # @param [String] input The GeoJSON document. This can be a feature collection, feature,
34
+ # or polygon. If a feature collection is provided, the first feature is used.
35
+ #
36
+ # @example Convert a GeoJSON document of Banbury to a set of nested coordinates.
37
+ # document = "{\"type\":\"Polygon\",\"coordinates\":[
38
+ # [
39
+ # [-1.7358398437499998,52.24630137198303], [-1.8923950195312498,52.05249047600099],
40
+ # [-1.56829833984375,51.891749018068246], [-1.27716064453125,51.91208502557545],
41
+ # [-1.19476318359375,52.032218104145294], [-1.24420166015625,52.19413974159753],
42
+ # [-1.5902709960937498,52.24125614966341], [-1.7358398437499998,52.24630137198303]
43
+ # ],
44
+ # [
45
+ # [-1.58203125,52.12590076522272], [-1.476287841796875,52.12590076522272],
46
+ # [-1.46392822265625,52.075285904832334], [-1.58203125,52.06937709602395],
47
+ # [-1.58203125,52.12590076522272]
48
+ # ],
49
+ # [
50
+ # [-1.4556884765625,52.01531743663362], [-1.483154296875,51.97642166216334],
51
+ # [-1.3677978515625,51.96626938051444], [-1.3568115234375,52.0102459910103],
52
+ # [-1.4556884765625,52.01531743663362]
53
+ # ]
54
+ # ]}"
55
+ # H3.geo_json_to_coordinates(document)
56
+ # [
57
+ # [
58
+ # [52.24630137198303, -1.7358398437499998], [52.05249047600099, -1.8923950195312498],
59
+ # [51.891749018068246, -1.56829833984375], [51.91208502557545, -1.27716064453125],
60
+ # [52.032218104145294, -1.19476318359375], [52.19413974159753, -1.24420166015625],
61
+ # [52.24125614966341, -1.5902709960937498], [52.24630137198303, -1.7358398437499998]
62
+ # ],
63
+ # [
64
+ # [52.12590076522272, -1.58203125], [52.12590076522272, -1.476287841796875],
65
+ # [52.075285904832334, -1.46392822265625], [52.06937709602395, -1.58203125],
66
+ # [52.12590076522272, -1.58203125]
67
+ # ],
68
+ # [
69
+ # [52.01531743663362, -1.4556884765625], [51.97642166216334, -1.483154296875],
70
+ # [51.96626938051444, -1.3677978515625], [52.0102459910103, -1.3568115234375],
71
+ # [52.01531743663362, -1.4556884765625]
72
+ # ]
73
+ # ]
74
+ #
75
+ # @raise [ArgumentError] Failed to parse the GeoJSON document.
76
+ #
77
+ # @return [Array<Array<Array>>] Nested array of coordinates.
78
+ def geo_json_to_coordinates(input)
79
+ geom = RGeo::GeoJSON.decode(input)
80
+ coordinates = if geom.respond_to?(:first) # feature collection
81
+ geom.first.geometry.coordinates
82
+ elsif geom.respond_to?(:geometry) # feature
83
+ geom.geometry.coordinates
84
+ elsif geom.respond_to?(:coordinates) # polygon
85
+ geom.coordinates
86
+ else
87
+ failed_to_parse!
88
+ end
89
+ swap_lat_lon(coordinates) || failed_to_parse!
90
+ rescue JSON::ParserError
91
+ failed_to_parse!
92
+ end
93
+
94
+ # Convert a nested array of coordinates to a GeoJSON document
95
+ #
96
+ # @param [Array<Array<Array>>] coordinates Nested array of coordinates.
97
+ #
98
+ # @example Convert a set of nested coordinates of Banbury to a GeoJSON document.
99
+ # coordinates = [
100
+ # [
101
+ # [52.24630137198303, -1.7358398437499998], [52.05249047600099, -1.8923950195312498],
102
+ # [51.891749018068246, -1.56829833984375], [51.91208502557545, -1.27716064453125],
103
+ # [52.032218104145294, -1.19476318359375], [52.19413974159753, -1.24420166015625],
104
+ # [52.24125614966341, -1.5902709960937498], [52.24630137198303, -1.7358398437499998]
105
+ # ],
106
+ # [
107
+ # [52.12590076522272, -1.58203125], [52.12590076522272, -1.476287841796875],
108
+ # [52.075285904832334, -1.46392822265625], [52.06937709602395, -1.58203125],
109
+ # [52.12590076522272, -1.58203125]
110
+ # ],
111
+ # [
112
+ # [52.01531743663362, -1.4556884765625], [51.97642166216334, -1.483154296875],
113
+ # [51.96626938051444, -1.3677978515625], [52.0102459910103, -1.3568115234375],
114
+ # [52.01531743663362, -1.4556884765625]
115
+ # ]
116
+ # ]
117
+ # H3.coordinates_to_geo_json(coordinates)
118
+ # "{\"type\":\"Polygon\",\"coordinates\":[
119
+ # [
120
+ # [-1.7358398437499998,52.24630137198303], [-1.8923950195312498,52.05249047600099],
121
+ # [-1.56829833984375,51.891749018068246], [-1.27716064453125,51.91208502557545],
122
+ # [-1.19476318359375,52.032218104145294], [-1.24420166015625,52.19413974159753],
123
+ # [-1.5902709960937498,52.24125614966341], [-1.7358398437499998,52.24630137198303]
124
+ # ],
125
+ # [
126
+ # [-1.58203125,52.12590076522272], [-1.476287841796875,52.12590076522272],
127
+ # [-1.46392822265625,52.075285904832334], [-1.58203125,52.06937709602395],
128
+ # [-1.58203125,52.12590076522272]
129
+ # ],
130
+ # [
131
+ # [-1.4556884765625,52.01531743663362], [-1.483154296875,51.97642166216334],
132
+ # [-1.3677978515625,51.96626938051444], [-1.3568115234375,52.0102459910103],
133
+ # [-1.4556884765625,52.01531743663362]
134
+ # ]
135
+ # ]}"
136
+ #
137
+ # @raise [ArgumentError] Failed to parse the given coordinates.
138
+ #
139
+ # @return [String] GeoJSON document.
140
+ def coordinates_to_geo_json(coordinates)
141
+ coordinates = swap_lat_lon(coordinates)
142
+ outer_coords, *inner_coords = coordinates
143
+ factory = RGeo::Cartesian.simple_factory
144
+ exterior = factory.linear_ring(outer_coords.map { |lon, lat| factory.point(lon, lat) })
145
+ interior_rings = inner_coords.map do |polygon|
146
+ factory.linear_ring(polygon.map { |lon, lat| factory.point(lon, lat) })
147
+ end
148
+ polygon = factory.polygon(exterior, interior_rings)
149
+ RGeo::GeoJSON.encode(polygon).to_json
150
+ rescue RGeo::Error::InvalidGeometry, NoMethodError
151
+ invalid_coordinates!
152
+ end
153
+
154
+ private
155
+
156
+ # geo-json coordinates use [lon, lat], h3 uses [lat, lon]
157
+ def swap_lat_lon(coordinates)
158
+ coordinates.map { |polygon| polygon.map { |x, y| [y, x] } }
159
+ end
160
+
161
+ def failed_to_parse!
162
+ raise ArgumentError, "Could not parse given input. Please use a GeoJSON polygon."
163
+ end
164
+
165
+ def invalid_coordinates!
166
+ raise ArgumentError, "Could not parse given coordinates."
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,160 @@
1
+ module H3
2
+ # Hierarchical grid functions.
3
+ #
4
+ # @see https://uber.github.io/h3/#/documentation/api-reference/hierarchy
5
+ module Hierarchy
6
+ extend H3::Bindings::Base
7
+
8
+ # @!method h3_to_parent(h3_index, parent_resolution)
9
+ #
10
+ # Derive the parent hexagon which contains the hexagon at the given H3 index.
11
+ #
12
+ # @param [Integer] h3_index A valid H3 index.
13
+ # @param [Integer] parent_resoluton The desired resolution of the parent hexagon.
14
+ #
15
+ # @example Find the parent hexagon for a H3 index.
16
+ # H3.h3_to_parent(613196570357137407, 6)
17
+ # 604189371209351167
18
+ #
19
+ # @return [Integer] H3 index of parent hexagon.
20
+ attach_function :h3_to_parent, :h3ToParent, [ :h3_index, :int ], :h3_index
21
+
22
+ # @!method max_h3_to_children_size(h3_index, child_resolution)
23
+ #
24
+ # Derive maximum number of child hexagons possible at given resolution.
25
+ #
26
+ # @param [Integer] h3_index A valid H3 index.
27
+ # @param [Integer] child_resoluton The desired resolution of the child hexagons.
28
+ #
29
+ # @example Derive maximum number of child hexagons.
30
+ # H3.max_h3_to_children_size(613196570357137407, 10)
31
+ # 49
32
+ #
33
+ # @return [Integer] Maximum number of child hexagons possible at given resolution.
34
+ attach_function :max_h3_to_children_size, :maxH3ToChildrenSize, [ :h3_index, :int ], :int
35
+
36
+ # Derive child hexagons contained within the hexagon at the given H3 index.
37
+ #
38
+ # @param [Integer] h3_index A valid H3 index.
39
+ # @param [Integer] child_resolution The desired resolution of hexagons returned.
40
+ #
41
+ # @example Find the child hexagons for a H3 index.
42
+ # H3.h3_to_children(613196570357137407, 9)
43
+ # [
44
+ # 617700169982672895, 617700169982935039, 617700169983197183, 617700169983459327,
45
+ # 617700169983721471, 617700169983983615, 617700169984245759
46
+ # ]
47
+ #
48
+ # @return [Array<Integer>] H3 indexes of child hexagons.
49
+ def h3_to_children(h3_index, child_resolution)
50
+ max_children = max_h3_to_children_size(h3_index, child_resolution)
51
+ h3_children = FFI::MemoryPointer.new(H3_INDEX, max_children)
52
+ Bindings::Private.h3_to_children(h3_index, child_resolution, h3_children)
53
+ h3_children.read_array_of_ulong_long(max_children).reject(&:zero?)
54
+ end
55
+
56
+ # Find the maximum uncompacted size of the given set of H3 indexes.
57
+ #
58
+ # @param [Array<Integer>] compacted_set An array of valid H3 indexes.
59
+ # @param [Integer] resolution The desired resolution to uncompact to.
60
+ #
61
+ # @example Find the maximum uncompacted size of the given set.
62
+ # h3_set = [
63
+ # 617700440093229055, 617700440092704767, 617700440100569087, 617700440013012991,
64
+ # 617700440013275135, 617700440092180479, 617700440091656191, 617700440092966911,
65
+ # 617700440100831231, 617700440100044799, 617700440101617663, 617700440081956863,
66
+ # 613196840447246335
67
+ # ]
68
+ # H3.max_uncompact_size(h3_set, 10)
69
+ # 133
70
+ #
71
+ # @raise [ArgumentError] Given resolution is invalid for h3_set.
72
+ #
73
+ # @return [Integer] Maximum size of uncompacted set.
74
+ def max_uncompact_size(compacted_set, resolution)
75
+ compacted_set = compacted_set.uniq
76
+ FFI::MemoryPointer.new(H3_INDEX, compacted_set.size) do |hexagons_ptr|
77
+ hexagons_ptr.write_array_of_ulong_long(compacted_set)
78
+ size = Bindings::Private.max_uncompact_size(hexagons_ptr, compacted_set.size, resolution)
79
+ raise(ArgumentError, "Couldn't estimate size. Invalid resolution?") if size < 0
80
+ return size
81
+ end
82
+ end
83
+
84
+ # Compact a set of H3 indexes as best as possible.
85
+ #
86
+ # In the case where the set cannot be compacted, the set is returned unchanged.
87
+ #
88
+ # @param [Array<Integer>] h3_set An array of valid H3 indexes.
89
+ #
90
+ # @example Compact the given set.
91
+ # h3_set = [
92
+ # 617700440073043967, 617700440072781823, 617700440073568255, 617700440093229055,
93
+ # 617700440092704767, 617700440100569087, 617700440074092543, 617700440073830399,
94
+ # 617700440074354687, 617700440073306111, 617700440013012991, 617700440013275135,
95
+ # 617700440092180479, 617700440091656191, 617700440092966911, 617700440100831231,
96
+ # 617700440100044799, 617700440101617663, 617700440081956863
97
+ # ]
98
+ # H3.compact(h3_set)
99
+ # [
100
+ # 617700440093229055, 617700440092704767, 617700440100569087, 617700440013012991,
101
+ # 617700440013275135, 617700440092180479, 617700440091656191, 617700440092966911,
102
+ # 617700440100831231, 617700440100044799, 617700440101617663, 617700440081956863,
103
+ # 613196840447246335
104
+ # ]
105
+ #
106
+ # @raise [RuntimeError] Couldn't attempt to compact given H3 indexes.
107
+ #
108
+ # @return [Array<Integer>] Compacted set of H3 indexes.
109
+ def compact(h3_set)
110
+ h3_set = h3_set.uniq
111
+ failure = false
112
+ out = FFI::MemoryPointer.new(H3_INDEX, h3_set.size)
113
+ FFI::MemoryPointer.new(H3_INDEX, h3_set.size) do |hexagons_ptr|
114
+ hexagons_ptr.write_array_of_ulong_long(h3_set)
115
+ failure = Bindings::Private.compact(hexagons_ptr, out, h3_set.size)
116
+ end
117
+
118
+ raise "Couldn't compact given indexes" if failure
119
+ out.read_array_of_ulong_long(h3_set.size).reject(&:zero?)
120
+ end
121
+
122
+ # Uncompact a set of H3 indexes to the given resolution.
123
+ #
124
+ # @param [Array<Integer>] compacted_set An array of valid H3 indexes.
125
+ # @param [Integer] resolution The desired resolution to uncompact to.
126
+ #
127
+ # @example Compact the given set.
128
+ # h3_set = [
129
+ # 617700440093229055, 617700440092704767, 617700440100569087, 617700440013012991,
130
+ # 617700440013275135, 617700440092180479, 617700440091656191, 617700440092966911,
131
+ # 617700440100831231, 617700440100044799, 617700440101617663, 617700440081956863,
132
+ # 613196840447246335
133
+ # ]
134
+ # H3.uncompact(h3_set)
135
+ # [
136
+ # 617700440093229055, 617700440092704767, 617700440100569087, 617700440013012991,
137
+ # 617700440013275135, 617700440092180479, 617700440091656191, 617700440092966911,
138
+ # 617700440100831231, 617700440100044799, 617700440101617663, 617700440081956863,
139
+ # 617700440072781823, 617700440073043967, 617700440073306111, 617700440073568255,
140
+ # 617700440073830399, 617700440074092543, 617700440074354687
141
+ # ]
142
+ #
143
+ # @raise [RuntimeError] Couldn't attempt to umcompact H3 indexes.
144
+ #
145
+ # @return [Array<Integer>] Uncompacted set of H3 indexes.
146
+ def uncompact(compacted_set, resolution)
147
+ max_size = max_uncompact_size(compacted_set, resolution)
148
+
149
+ failure = false
150
+ out = FFI::MemoryPointer.new(H3_INDEX, max_size)
151
+ FFI::MemoryPointer.new(H3_INDEX, compacted_set.size) do |hexagons_ptr|
152
+ hexagons_ptr.write_array_of_ulong_long(compacted_set)
153
+ failure = Bindings::Private.uncompact(hexagons_ptr, compacted_set.size, out, max_size, resolution)
154
+ end
155
+
156
+ raise "Couldn't uncompact given indexes" if failure
157
+ out.read_array_of_ulong_long(max_size).reject(&:zero?)
158
+ end
159
+ end
160
+ end