Skip to content

Commit 9731b6d

Browse files
committed
WIP
1 parent 029c9b2 commit 9731b6d

21 files changed

+905
-783
lines changed

Gemfile.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ PATH
99
GEM
1010
remote: https://rubygems.org/
1111
specs:
12+
coderay (1.1.3)
1213
diff-lcs (1.4.4)
1314
ffi (1.15.4)
15+
method_source (1.0.0)
16+
pry (0.14.1)
17+
coderay (~> 1.1)
18+
method_source (~> 1.0)
1419
rake (13.0.6)
1520
rgeo (2.3.0)
1621
rgeo-geojson (2.1.1)
@@ -36,6 +41,7 @@ PLATFORMS
3641

3742
DEPENDENCIES
3843
h3!
44+
pry (~> 0.14)
3945
rake (~> 13.0)
4046
rspec (~> 3.8)
4147
yard (~> 0.9)

h3.gemspec

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
66
spec.licenses = ["MIT"]
77
spec.summary = "C Bindings for Uber's H3 library"
88
spec.homepage = "https://github.com/StuartApp/h3_ruby"
9-
spec.authors = ["Lachlan Laycock", "Sean Handley"]
10-
spec.email = "l.laycock@stuart.com"
9+
spec.authors = ["Lachlan Laycock", "Sean Handley", "Xavier Noria"]
10+
spec.email = "s.handley@stuart.com"
1111

1212
spec.required_ruby_version = ">= 2.5"
1313
spec.files = `git ls-files --recurse-submodules`.split("\n")
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
2020
spec.add_development_dependency "rake", "~> 13.0"
2121
spec.add_development_dependency "rspec", "~> 3.8"
2222
spec.add_development_dependency "yard", "~> 0.9"
23+
spec.add_development_dependency "pry", "~> 0.14"
2324

2425
spec.extensions << "ext/h3/extconf.rb"
2526
end

lib/h3/bindings/base.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def self.extended(base)
1313
base.ffi_lib ["#{lib_path}/libh3.dylib", "#{lib_path}/libh3.so"]
1414
base.typedef :ulong_long, :h3_index
1515
base.typedef :int, :k_distance
16+
base.typedef :uint, :h3_error_code
1617
end
1718

1819
def attach_predicate_function(name, *args)

lib/h3/bindings/error.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module H3
2+
module Bindings
3+
module Error
4+
class FailedError < StandardError ; end
5+
class DomainError < StandardError ; end
6+
class LatLngDomainError < StandardError ; end
7+
class ResolutionDomainError < StandardError ; end
8+
class CellInvalidError < StandardError ; end
9+
class DirectedEdgeInvalidError < StandardError ; end
10+
class UndirectedEdgeInvalidError < StandardError ; end
11+
class VertexInvalidError < StandardError ; end
12+
class PentagonDistortionError < StandardError ; end
13+
class DuplicateInputError < StandardError ; end
14+
class NotNeighborsError < StandardError ; end
15+
class ResolutionMismatchError < StandardError ; end
16+
class MemoryAllocationError < StandardError ; end
17+
class MemoryBoundsError < StandardError ; end
18+
19+
def self.raise_error(code)
20+
case code
21+
when 1 then raise FailedError
22+
when 2 then raise DomainError
23+
when 3 then raise LatLngDomainError
24+
when 4 then raise ResolutionDomainError
25+
when 5 then raise CellInvalidError
26+
when 6 then raise DirectedEdgeInvalidError
27+
when 7 then raise UndirectedEdgeInvalidError
28+
when 8 then raise VertexInvalidError
29+
when 9 then raise PentagonDistortionError
30+
when 10 then raise DuplicateInputError
31+
when 11 then raise NotNeighborsError
32+
when 12 then raise ResolutionMismatchError
33+
when 13 then raise MemoryAllocationError
34+
when 14 then raise MemoryBoundsError
35+
end
36+
end
37+
end
38+
end
39+
end

lib/h3/bindings/private.rb

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,71 @@ module Bindings
77
module Private
88
extend H3::Bindings::Base
99

10-
attach_function :compact, [H3IndexesIn, H3IndexesOut, :size_t], :bool
11-
attach_function :destroy_linked_polygon, :destroyLinkedPolygon, [LinkedGeoPolygon], :void
12-
attach_function :geo_to_h3, :geoToH3, [GeoCoord, Resolution], :h3_index
13-
attach_function :get_pentagon_indexes, :getPentagonIndexes, [:int, H3IndexesOut], :void
14-
attach_function :h3_faces, :h3GetFaces, %i[h3_index buffer_out], :void
15-
attach_function :h3_indexes_from_unidirectional_edge,
16-
:getH3IndexesFromUnidirectionalEdge,
17-
[:h3_index, H3IndexesOut], :void
18-
attach_function :h3_line, :h3Line, [:h3_index, :h3_index, H3IndexesOut], :int
19-
attach_function :h3_unidirectional_edges_from_hexagon,
20-
:getH3UnidirectionalEdgesFromHexagon,
21-
[:h3_index, H3IndexesOut], :void
22-
attach_function :h3_set_to_linked_geo,
23-
:h3SetToLinkedGeo,
24-
[H3IndexesIn, :size_t, LinkedGeoPolygon],
25-
:void
26-
attach_function :h3_to_children, :h3ToChildren, [:h3_index, Resolution, H3IndexesOut], :void
27-
attach_function :h3_to_geo, :h3ToGeo, [:h3_index, GeoCoord], :void
28-
attach_function :h3_to_string, :h3ToString, %i[h3_index buffer_out size_t], :void
10+
def self.safe_call(out_type, method, *in_args)
11+
out = FFI::MemoryPointer.new(out_type)
12+
send(method, *in_args + [out]).tap do |code|
13+
Error::raise_error(code) unless code.zero?
14+
end
15+
out.send("read_#{out_type}".to_sym)
16+
end
17+
18+
attach_function :cellToParent, [:h3_index, Resolution, H3Index], :h3_error_code
19+
attach_function :compactCells, [H3IndexesIn, H3IndexesOut, :int64], :h3_error_code
20+
# attach_function :destroy_linked_polygon, :destroyLinkedPolygon, [LinkedGeoPolygon], :void
21+
attach_function :edge_length_km, :getHexagonEdgeLengthAvgKm, [Resolution, :pointer], :h3_error_code
22+
attach_function :from_string, :stringToH3, %i[string pointer], :h3_error_code
23+
attach_function :geo_to_h3, :latLngToCell, [LatLng, Resolution, :pointer], :h3_error_code
24+
# attach_function :get_pentagon_indexes, :getPentagonIndexes, [:int, H3IndexesOut], :void
25+
attach_function :hexagon_count, :getNumCells, [Resolution, :pointer], :h3_error_code
26+
attach_function :h3_faces, :getIcosahedronFaces, %i[h3_index buffer_out], :h3_error_code
27+
# attach_function :h3_indexes_from_unidirectional_edge,
28+
# :getH3IndexesFromUnidirectionalEdge,
29+
# [:h3_index, H3IndexesOut], :void
30+
# attach_function :h3_line, :h3Line, [:h3_index, :h3_index, H3IndexesOut], :int
31+
# attach_function :h3_unidirectional_edges_from_hexagon,
32+
# :getH3UnidirectionalEdgesFromHexagon,
33+
# [:h3_index, H3IndexesOut], :void
34+
# attach_function :h3_set_to_linked_geo,
35+
# :h3SetToLinkedGeo,
36+
# [H3IndexesIn, :size_t, LinkedGeoPolygon],
37+
# :void
38+
attach_function :h3_to_children, :cellToChildren, [:h3_index, Resolution, H3IndexesOut], :h3_error_code
39+
attach_function :h3_to_geo, :cellToLatLng, [:h3_index, LatLng], :h3_error_code
40+
attach_function :h3_to_string, :h3ToString, %i[h3_index buffer_out size_t], :h3_error_code
2941
attach_function :h3_to_geo_boundary,
30-
:h3ToGeoBoundary,
31-
[:h3_index, GeoBoundary],
32-
:void
33-
attach_function :h3_unidirectional_edge_boundary,
34-
:getH3UnidirectionalEdgeBoundary,
35-
[:h3_index, GeoBoundary], :void
36-
attach_function :hex_range, :hexRange, [:h3_index, :k_distance, H3IndexesOut], :bool
37-
attach_function :hex_range_distances,
38-
:hexRangeDistances,
39-
[:h3_index, :k_distance, H3IndexesOut, :buffer_out], :bool
40-
attach_function :hex_ranges,
41-
:hexRanges,
42-
[H3IndexesIn, :size_t, :k_distance, H3IndexesOut],
43-
:bool
44-
attach_function :hex_ring, :hexRing, [:h3_index, :k_distance, H3IndexesOut], :bool
45-
attach_function :k_ring, :kRing, [:h3_index, :k_distance, H3IndexesOut], :void
46-
attach_function :k_ring_distances,
47-
:kRingDistances,
48-
[:h3_index, :k_distance, H3IndexesOut, :buffer_out],
49-
:bool
50-
attach_function :max_polyfill_size,
51-
:maxPolyfillSize,
52-
[GeoPolygon, Resolution],
53-
:int
54-
attach_function :max_uncompact_size, :maxUncompactSize, [H3IndexesIn, :size_t, Resolution], :int
55-
attach_function :point_distance_rads, :pointDistRads, [GeoCoord, GeoCoord], :double
56-
attach_function :point_distance_km, :pointDistKm, [GeoCoord, GeoCoord], :double
57-
attach_function :point_distance_m, :pointDistM, [GeoCoord, GeoCoord], :double
58-
attach_function :polyfill, [GeoPolygon, Resolution, H3IndexesOut], :void
59-
attach_function :res_0_indexes, :getRes0Indexes, [H3IndexesOut], :void
60-
attach_function :uncompact, [H3IndexesIn, :size_t, H3IndexesOut, :size_t, Resolution], :bool
42+
:cellToBoundary,
43+
[:h3_index, CellBoundary],
44+
:h3_error_code
45+
# attach_function :h3_unidirectional_edge_boundary,
46+
# :getH3UnidirectionalEdgeBoundary,
47+
# [:h3_index, CellBoundary], :void
48+
# attach_function :hex_range, :hexRange, [:h3_index, :k_distance, H3IndexesOut], :bool
49+
# attach_function :hex_range_distances,
50+
# :hexRangeDistances,
51+
# [:h3_index, :k_distance, H3IndexesOut, :buffer_out], :bool
52+
# attach_function :hex_ranges,
53+
# :hexRanges,
54+
# [H3IndexesIn, :size_t, :k_distance, H3IndexesOut],
55+
# :bool
56+
# attach_function :hex_ring, :hexRing, [:h3_index, :k_distance, H3IndexesOut], :bool
57+
attach_function :k_ring, :gridDisk, [:h3_index, :k_distance, H3IndexesOut], :h3_error_code
58+
# attach_function :k_ring_distances,
59+
# :kRingDistances,
60+
# [:h3_index, :k_distance, H3IndexesOut, :buffer_out],
61+
# :bool
62+
attach_function :max_children, :cellToChildrenSize, [:h3_index, Resolution, :pointer], :h3_error_code
63+
attach_function :max_face_count, :maxFaceCount, %i[h3_index pointer], :h3_error_code
64+
# attach_function :max_polyfill_size,
65+
# :maxPolyfillSize,
66+
# [GeoPolygon, Resolution],
67+
# :int
68+
attach_function :max_uncompact_size, :uncompactCellsSize, [H3IndexesIn, :int64, Resolution, :pointer], :h3_error_code
69+
# attach_function :point_distance_rads, :pointDistRads, [GeoCoord, GeoCoord], :double
70+
# attach_function :point_distance_km, :pointDistKm, [GeoCoord, GeoCoord], :double
71+
# attach_function :point_distance_m, :pointDistM, [GeoCoord, GeoCoord], :double
72+
# attach_function :polyfill, [GeoPolygon, Resolution, H3IndexesOut], :void
73+
attach_function :res_0_indexes, :getRes0Cells, [H3IndexesOut], :h3_error_code
74+
attach_function :uncompactCells, [H3IndexesIn, :size_t, H3IndexesOut, :size_t, Resolution], :h3_error_code
6175
end
6276
end
6377
end

lib/h3/bindings/structs.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ module Bindings
77
module Structs
88
extend FFI::Library
99

10-
class GeoCoord < FFI::Struct
10+
class LatLng < FFI::Struct
1111
layout :lat, :double,
1212
:lon, :double
1313
end
1414

15-
class GeoBoundary < FFI::Struct
15+
class CellBoundary < FFI::Struct
1616
layout :num_verts, :int,
17-
:verts, [GeoCoord, 10] # array of GeoCoord structs (must be fixed length)
17+
:verts, [LatLng, 10] # array of GeoCoord structs (must be fixed length)
1818
end
1919

2020
class GeoFence < FFI::Struct
@@ -34,7 +34,7 @@ class GeoMultiPolygon < FFI::Struct
3434
end
3535

3636
class LinkedGeoCoord < FFI::Struct
37-
layout :vertex, GeoCoord,
37+
layout :vertex, LatLng,
3838
:next, LinkedGeoCoord.ptr
3939
end
4040

lib/h3/bindings/types.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,29 @@ def to_native(value, _context)
2121
private
2222

2323
def failure
24-
raise ArgumentError,
24+
raise Error::ResolutionDomainError,
2525
"resolution must be between #{RES_RANGE.first} and #{RES_RANGE.last}"
2626
end
2727
end
2828
end
2929

30+
class H3Index
31+
extend FFI::DataConverter
32+
native_type FFI::Type::POINTER
33+
34+
def initialize(value)
35+
ptr.write(value)
36+
end
37+
38+
def size
39+
FFI.type_size(FFI::Type::ULONG_LONG)
40+
end
41+
42+
def ptr
43+
@ptr ||= FFI::MemoryPointer.new(:ulong_long)
44+
end
45+
end
46+
3047
class H3IndexesIn
3148
extend FFI::DataConverter
3249
native_type FFI::Type::POINTER

lib/h3/hierarchy.rb

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ module Hierarchy
1717
# 604189371209351167
1818
#
1919
# @return [Integer] H3 index of parent hexagon.
20-
attach_function :parent, :h3ToParent, [:h3_index, Resolution], :h3_index
20+
def parent(h3_index, parent_resolution)
21+
Bindings::Private.safe_call(:ulong_long, :cellToParent, h3_index, parent_resolution)
22+
end
2123

2224
# @!method max_children(h3_index, child_resolution)
2325
#
@@ -31,7 +33,9 @@ module Hierarchy
3133
# 49
3234
#
3335
# @return [Integer] Maximum number of child hexagons possible at given resolution.
34-
attach_function :max_children, :maxH3ToChildrenSize, [:h3_index, Resolution], :int
36+
def max_children(h3_index, child_resolution)
37+
Bindings::Private.safe_call(:int, :max_children, h3_index, child_resolution)
38+
end
3539

3640
# @!method center_child(h3_index, child_resolution)
3741
#
@@ -46,7 +50,7 @@ module Hierarchy
4650
# 622203769609814015
4751
#
4852
# @return [Integer] H3 index of center child hexagon.
49-
attach_function :center_child, :h3ToCenterChild, [:h3_index, Resolution], :h3_index
53+
attach_function :center_child, :cellToCenterChild, [:h3_index, Resolution], :h3_index
5054

5155
# Derive child hexagons contained within the hexagon at the given H3 index.
5256
#
@@ -87,10 +91,7 @@ def children(h3_index, child_resolution)
8791
#
8892
# @return [Integer] Maximum size of uncompacted set.
8993
def max_uncompact_size(compacted_set, resolution)
90-
h3_set = H3Indexes.with_contents(compacted_set)
91-
size = Bindings::Private.max_uncompact_size(h3_set, compacted_set.size, resolution)
92-
raise(ArgumentError, "Couldn't estimate size. Invalid resolution?") if size.negative?
93-
size
94+
Bindings::Private.safe_call(:int64, :max_uncompact_size, H3Indexes.with_contents(compacted_set), compacted_set.size, resolution)
9495
end
9596

9697
# Compact a set of H3 indexes as best as possible.
@@ -121,9 +122,9 @@ def max_uncompact_size(compacted_set, resolution)
121122
def compact(h3_set)
122123
h3_set = H3Indexes.with_contents(h3_set)
123124
out = H3Indexes.of_size(h3_set.size)
124-
failure = Bindings::Private.compact(h3_set, out, out.size)
125-
126-
raise "Couldn't compact given indexes" if failure
125+
Bindings::Private.compactCells(h3_set, out, out.size).tap do |code|
126+
Bindings::Error::raise_error(code) unless code.zero?
127+
end
127128
out.read
128129
end
129130

@@ -156,9 +157,10 @@ def uncompact(compacted_set, resolution)
156157

157158
out = H3Indexes.of_size(max_size)
158159
h3_set = H3Indexes.with_contents(compacted_set)
159-
failure = Bindings::Private.uncompact(h3_set, compacted_set.size, out, max_size, resolution)
160+
Bindings::Private.uncompactCells(h3_set, compacted_set.size, out, max_size, resolution).tap do |code|
161+
Bindings::Error::raise_error(code) unless code.zero?
162+
end
160163

161-
raise "Couldn't uncompact given indexes" if failure
162164
out.read
163165
end
164166
end

lib/h3/indexing.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ def from_geo_coordinates(coords, resolution)
2929
raise(ArgumentError, "Invalid coordinates")
3030
end
3131

32-
coords = GeoCoord.new
32+
coords = LatLng.new
3333
coords[:lat] = degs_to_rads(lat)
3434
coords[:lon] = degs_to_rads(lon)
35-
Bindings::Private.geo_to_h3(coords, resolution)
35+
36+
Bindings::Private.safe_call(:ulong_long, :geo_to_h3, coords, resolution)
3637
end
3738

3839
# Derive coordinates for a given H3 index.
@@ -47,8 +48,10 @@ def from_geo_coordinates(coords, resolution)
4748
#
4849
# @return [Array<Integer>] A coordinate pair.
4950
def to_geo_coordinates(h3_index)
50-
coords = GeoCoord.new
51-
Bindings::Private.h3_to_geo(h3_index, coords)
51+
coords = LatLng.new
52+
Bindings::Private.h3_to_geo(h3_index, coords).tap do |code|
53+
Bindings::Error::raise_error(code) unless code.zero?
54+
end
5255
[rads_to_degs(coords[:lat]), rads_to_degs(coords[:lon])]
5356
end
5457

@@ -71,7 +74,7 @@ def to_geo_coordinates(h3_index)
7174
#
7275
# @return [Array<Array<Integer>>] An array of six coordinate pairs.
7376
def to_boundary(h3_index)
74-
geo_boundary = GeoBoundary.new
77+
geo_boundary = CellBoundary.new
7578
Bindings::Private.h3_to_geo_boundary(h3_index, geo_boundary)
7679
geo_boundary[:verts].take(geo_boundary[:num_verts]).map do |d|
7780
[rads_to_degs(d[:lat]), rads_to_degs(d[:lon])]

0 commit comments

Comments
 (0)