h3 3.2.0

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