From 51b87528eee8ba61184c11450e58a38aaa6edd00 Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Mon, 7 Feb 2022 20:35:00 +0000 Subject: [PATCH 01/10] WIP --- Gemfile.lock | 9 +- h3.gemspec | 2 + lib/h3/bindings/base.rb | 1 + lib/h3/bindings/error.rb | 39 ++ lib/h3/bindings/private.rb | 117 ++--- lib/h3/bindings/structs.rb | 8 +- lib/h3/bindings/types.rb | 19 +- lib/h3/hierarchy.rb | 26 +- lib/h3/indexing.rb | 13 +- lib/h3/inspection.rb | 26 +- lib/h3/miscellaneous.rb | 22 +- lib/h3/traversal.rb | 17 +- lib/h3/unidirectional_edges.rb | 12 +- lib/h3/version.rb | 2 +- spec/hierarchy_spec.rb | 56 ++- spec/indexing_spec.rb | 2 + spec/miscellaneous_spec.rb | 306 ++++++------- spec/regions_spec.rb | 110 ++--- spec/spec_helper.rb | 2 + spec/traversal_spec.rb | 732 +++++++++++++++--------------- spec/unidirectional_edges_spec.rb | 174 +++---- 21 files changed, 907 insertions(+), 788 deletions(-) create mode 100644 lib/h3/bindings/error.rb diff --git a/Gemfile.lock b/Gemfile.lock index ad13388..ad930a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - h3 (3.7.4) + h3 (4.0.0) ffi (~> 1.9) rgeo-geojson (~> 2.1) zeitwerk (~> 2.5) @@ -12,6 +12,10 @@ GEM diff-lcs (1.5.1) ffi (1.17.1) multi_json (1.15.0) + method_source (1.0.0) + pry (0.14.1) + coderay (~> 1.1) + method_source (~> 1.0) rake (13.2.1) rgeo (3.0.0) rgeo-geojson (2.2.0) @@ -38,9 +42,10 @@ PLATFORMS DEPENDENCIES h3! + pry (~> 0.14) rake (~> 13.0) rspec (~> 3.8) yard (~> 0.9) BUNDLED WITH - 2.2.22 + 2.1.4 \ No newline at end of file diff --git a/h3.gemspec b/h3.gemspec index 49e90ab..64e377c 100644 --- a/h3.gemspec +++ b/h3.gemspec @@ -5,6 +5,7 @@ Gem::Specification.new do |spec| spec.version = H3::VERSION spec.licenses = ["MIT"] spec.summary = "C Bindings for Uber's H3 library" + spec.homepage = "https://github.com/seanhandley/h3_ruby" spec.authors = ["Sean Handley", "Xavier Noria", "Lachlan Laycock"] spec.email = "sean.handley@gmail.com" @@ -20,6 +21,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "rspec", "~> 3.8" spec.add_development_dependency "yard", "~> 0.9" + spec.add_development_dependency "pry", "~> 0.14" spec.extensions << "ext/h3/extconf.rb" end diff --git a/lib/h3/bindings/base.rb b/lib/h3/bindings/base.rb index cb52508..05220bd 100644 --- a/lib/h3/bindings/base.rb +++ b/lib/h3/bindings/base.rb @@ -13,6 +13,7 @@ def self.extended(base) base.ffi_lib ["#{lib_path}/libh3.dylib", "#{lib_path}/libh3.so"] base.typedef :ulong_long, :h3_index base.typedef :int, :k_distance + base.typedef :uint, :h3_error_code end def attach_predicate_function(name, *args) diff --git a/lib/h3/bindings/error.rb b/lib/h3/bindings/error.rb new file mode 100644 index 0000000..65160ae --- /dev/null +++ b/lib/h3/bindings/error.rb @@ -0,0 +1,39 @@ +module H3 + module Bindings + module Error + class FailedError < StandardError ; end + class DomainError < StandardError ; end + class LatLngDomainError < StandardError ; end + class ResolutionDomainError < StandardError ; end + class CellInvalidError < StandardError ; end + class DirectedEdgeInvalidError < StandardError ; end + class UndirectedEdgeInvalidError < StandardError ; end + class VertexInvalidError < StandardError ; end + class PentagonDistortionError < StandardError ; end + class DuplicateInputError < StandardError ; end + class NotNeighborsError < StandardError ; end + class ResolutionMismatchError < StandardError ; end + class MemoryAllocationError < StandardError ; end + class MemoryBoundsError < StandardError ; end + + def self.raise_error(code) + case code + when 1 then raise FailedError + when 2 then raise DomainError + when 3 then raise LatLngDomainError + when 4 then raise ResolutionDomainError + when 5 then raise CellInvalidError + when 6 then raise DirectedEdgeInvalidError + when 7 then raise UndirectedEdgeInvalidError + when 8 then raise VertexInvalidError + when 9 then raise PentagonDistortionError + when 10 then raise DuplicateInputError + when 11 then raise NotNeighborsError + when 12 then raise ResolutionMismatchError + when 13 then raise MemoryAllocationError + when 14 then raise MemoryBoundsError + end + end + end + end +end diff --git a/lib/h3/bindings/private.rb b/lib/h3/bindings/private.rb index 05145b3..e0f463c 100644 --- a/lib/h3/bindings/private.rb +++ b/lib/h3/bindings/private.rb @@ -7,58 +7,71 @@ module Bindings module Private extend H3::Bindings::Base - attach_function :compact, [H3IndexesIn, H3IndexesOut, :size_t], :bool - attach_function :destroy_linked_polygon, :destroyLinkedPolygon, [LinkedGeoPolygon], :void - attach_function :geo_to_h3, :geoToH3, [GeoCoord, Resolution], :h3_index - attach_function :get_pentagon_indexes, :getPentagonIndexes, [:int, H3IndexesOut], :void - attach_function :h3_faces, :h3GetFaces, %i[h3_index buffer_out], :void - attach_function :h3_indexes_from_unidirectional_edge, - :getH3IndexesFromUnidirectionalEdge, - [:h3_index, H3IndexesOut], :void - attach_function :h3_line, :h3Line, [:h3_index, :h3_index, H3IndexesOut], :int - attach_function :h3_unidirectional_edges_from_hexagon, - :getH3UnidirectionalEdgesFromHexagon, - [:h3_index, H3IndexesOut], :void - attach_function :h3_set_to_linked_geo, - :h3SetToLinkedGeo, - [H3IndexesIn, :size_t, LinkedGeoPolygon], - :void - attach_function :h3_to_children, :h3ToChildren, [:h3_index, Resolution, H3IndexesOut], :void - attach_function :h3_to_geo, :h3ToGeo, [:h3_index, GeoCoord], :void - attach_function :h3_to_string, :h3ToString, %i[h3_index buffer_out size_t], :void + def self.safe_call(out_type, method, *in_args) + out = FFI::MemoryPointer.new(out_type) + send(method, *in_args + [out]).tap do |code| + Error::raise_error(code) unless code.zero? + end + out.send("read_#{out_type}".to_sym) + end + + attach_function :cellToParent, [:h3_index, Resolution, H3Index], :h3_error_code + attach_function :compactCells, [H3IndexesIn, H3IndexesOut, :int64], :h3_error_code + # attach_function :destroy_linked_polygon, :destroyLinkedPolygon, [LinkedGeoPolygon], :void + attach_function :edge_length_km, :getHexagonEdgeLengthAvgKm, [Resolution, :pointer], :h3_error_code + attach_function :from_string, :stringToH3, %i[string pointer], :h3_error_code + attach_function :geo_to_h3, :latLngToCell, [LatLng, Resolution, :pointer], :h3_error_code + # attach_function :get_pentagon_indexes, :getPentagonIndexes, [:int, H3IndexesOut], :void + attach_function :hexagon_count, :getNumCells, [Resolution, :pointer], :h3_error_code + attach_function :h3_faces, :getIcosahedronFaces, %i[h3_index buffer_out], :h3_error_code + # attach_function :h3_indexes_from_unidirectional_edge, + # :getH3IndexesFromUnidirectionalEdge, + # [:h3_index, H3IndexesOut], :void + # attach_function :h3_line, :h3Line, [:h3_index, :h3_index, H3IndexesOut], :int + # attach_function :h3_unidirectional_edges_from_hexagon, + # :getH3UnidirectionalEdgesFromHexagon, + # [:h3_index, H3IndexesOut], :void + # attach_function :h3_set_to_linked_geo, + # :h3SetToLinkedGeo, + # [H3IndexesIn, :size_t, LinkedGeoPolygon], + # :void + attach_function :h3_to_children, :cellToChildren, [:h3_index, Resolution, H3IndexesOut], :h3_error_code + attach_function :h3_to_geo, :cellToLatLng, [:h3_index, LatLng], :h3_error_code + attach_function :h3_to_string, :h3ToString, %i[h3_index buffer_out size_t], :h3_error_code attach_function :h3_to_geo_boundary, - :h3ToGeoBoundary, - [:h3_index, GeoBoundary], - :void - attach_function :h3_unidirectional_edge_boundary, - :getH3UnidirectionalEdgeBoundary, - [:h3_index, GeoBoundary], :void - attach_function :hex_range, :hexRange, [:h3_index, :k_distance, H3IndexesOut], :bool - attach_function :hex_range_distances, - :hexRangeDistances, - [:h3_index, :k_distance, H3IndexesOut, :buffer_out], :bool - attach_function :hex_ranges, - :hexRanges, - [H3IndexesIn, :size_t, :k_distance, H3IndexesOut], - :bool - attach_function :hex_ring, :hexRing, [:h3_index, :k_distance, H3IndexesOut], :bool - attach_function :k_ring, :kRing, [:h3_index, :k_distance, H3IndexesOut], :void - attach_function :k_ring_distances, - :kRingDistances, - [:h3_index, :k_distance, H3IndexesOut, :buffer_out], - :bool - attach_function :max_polyfill_size, - :maxPolyfillSize, - [GeoPolygon, Resolution], - :int - attach_function :max_uncompact_size, :maxUncompactSize, [H3IndexesIn, :size_t, Resolution], :int - attach_function :point_distance_rads, :pointDistRads, [GeoCoord, GeoCoord], :double - attach_function :point_distance_km, :pointDistKm, [GeoCoord, GeoCoord], :double - attach_function :point_distance_m, :pointDistM, [GeoCoord, GeoCoord], :double - attach_function :polyfill, [GeoPolygon, Resolution, H3IndexesOut], :void - attach_function :res_0_indexes, :getRes0Indexes, [H3IndexesOut], :void - attach_function :string_to_h3, :stringToH3, %i[string], :h3_index - attach_function :uncompact, [H3IndexesIn, :size_t, H3IndexesOut, :size_t, Resolution], :bool + :cellToBoundary, + [:h3_index, CellBoundary], + :h3_error_code + # attach_function :h3_unidirectional_edge_boundary, + # :getH3UnidirectionalEdgeBoundary, + # [:h3_index, CellBoundary], :void + # attach_function :hex_range, :hexRange, [:h3_index, :k_distance, H3IndexesOut], :bool + # attach_function :hex_range_distances, + # :hexRangeDistances, + # [:h3_index, :k_distance, H3IndexesOut, :buffer_out], :bool + # attach_function :hex_ranges, + # :hexRanges, + # [H3IndexesIn, :size_t, :k_distance, H3IndexesOut], + # :bool + # attach_function :hex_ring, :hexRing, [:h3_index, :k_distance, H3IndexesOut], :bool + attach_function :k_ring, :gridDisk, [:h3_index, :k_distance, H3IndexesOut], :h3_error_code + # attach_function :k_ring_distances, + # :kRingDistances, + # [:h3_index, :k_distance, H3IndexesOut, :buffer_out], + # :bool + attach_function :max_children, :cellToChildrenSize, [:h3_index, Resolution, :pointer], :h3_error_code + attach_function :max_face_count, :maxFaceCount, %i[h3_index pointer], :h3_error_code + # attach_function :max_polyfill_size, + # :maxPolyfillSize, + # [GeoPolygon, Resolution], + # :int + attach_function :max_uncompact_size, :uncompactCellsSize, [H3IndexesIn, :int64, Resolution, :pointer], :h3_error_code + # attach_function :point_distance_rads, :pointDistRads, [GeoCoord, GeoCoord], :double + # attach_function :point_distance_km, :pointDistKm, [GeoCoord, GeoCoord], :double + # attach_function :point_distance_m, :pointDistM, [GeoCoord, GeoCoord], :double + # attach_function :polyfill, [GeoPolygon, Resolution, H3IndexesOut], :void + attach_function :res_0_indexes, :getRes0Cells, [H3IndexesOut], :h3_error_code + attach_function :uncompactCells, [H3IndexesIn, :size_t, H3IndexesOut, :size_t, Resolution], :h3_error_code end end -end +end \ No newline at end of file diff --git a/lib/h3/bindings/structs.rb b/lib/h3/bindings/structs.rb index c2b9a74..1232979 100644 --- a/lib/h3/bindings/structs.rb +++ b/lib/h3/bindings/structs.rb @@ -7,14 +7,14 @@ module Bindings module Structs extend FFI::Library - class GeoCoord < FFI::Struct + class LatLng < FFI::Struct layout :lat, :double, :lon, :double end - class GeoBoundary < FFI::Struct + class CellBoundary < FFI::Struct layout :num_verts, :int, - :verts, [GeoCoord, 10] # array of GeoCoord structs (must be fixed length) + :verts, [LatLng, 10] # array of GeoCoord structs (must be fixed length) end class GeoFence < FFI::Struct @@ -34,7 +34,7 @@ class GeoMultiPolygon < FFI::Struct end class LinkedGeoCoord < FFI::Struct - layout :vertex, GeoCoord, + layout :vertex, LatLng, :next, LinkedGeoCoord.ptr end diff --git a/lib/h3/bindings/types.rb b/lib/h3/bindings/types.rb index 118dcb1..edd1ccf 100644 --- a/lib/h3/bindings/types.rb +++ b/lib/h3/bindings/types.rb @@ -21,12 +21,29 @@ def to_native(value, _context) private def failure - raise ArgumentError, + raise Error::ResolutionDomainError, "resolution must be between #{RES_RANGE.first} and #{RES_RANGE.last}" end end end + class H3Index + extend FFI::DataConverter + native_type FFI::Type::POINTER + + def initialize(value) + ptr.write(value) + end + + def size + FFI.type_size(FFI::Type::ULONG_LONG) + end + + def ptr + @ptr ||= FFI::MemoryPointer.new(:ulong_long) + end + end + class H3IndexesIn extend FFI::DataConverter native_type FFI::Type::POINTER diff --git a/lib/h3/hierarchy.rb b/lib/h3/hierarchy.rb index 17ffba1..401dd2b 100644 --- a/lib/h3/hierarchy.rb +++ b/lib/h3/hierarchy.rb @@ -17,7 +17,9 @@ module Hierarchy # 604189371209351167 # # @return [Integer] H3 index of parent hexagon. - attach_function :parent, :h3ToParent, [:h3_index, Resolution], :h3_index + def parent(h3_index, parent_resolution) + Bindings::Private.safe_call(:ulong_long, :cellToParent, h3_index, parent_resolution) + end # @!method max_children(h3_index, child_resolution) # @@ -31,7 +33,9 @@ module Hierarchy # 49 # # @return [Integer] Maximum number of child hexagons possible at given resolution. - attach_function :max_children, :maxH3ToChildrenSize, [:h3_index, Resolution], :int + def max_children(h3_index, child_resolution) + Bindings::Private.safe_call(:int, :max_children, h3_index, child_resolution) + end # @!method center_child(h3_index, child_resolution) # @@ -46,7 +50,7 @@ module Hierarchy # 622203769609814015 # # @return [Integer] H3 index of center child hexagon. - attach_function :center_child, :h3ToCenterChild, [:h3_index, Resolution], :h3_index + attach_function :center_child, :cellToCenterChild, [:h3_index, Resolution], :h3_index # Derive child hexagons contained within the hexagon at the given H3 index. # @@ -87,10 +91,7 @@ def children(h3_index, child_resolution) # # @return [Integer] Maximum size of uncompacted set. def max_uncompact_size(compacted_set, resolution) - h3_set = H3Indexes.with_contents(compacted_set) - size = Bindings::Private.max_uncompact_size(h3_set, compacted_set.size, resolution) - raise(ArgumentError, "Couldn't estimate size. Invalid resolution?") if size.negative? - size + Bindings::Private.safe_call(:int64, :max_uncompact_size, H3Indexes.with_contents(compacted_set), compacted_set.size, resolution) end # Compact a set of H3 indexes as best as possible. @@ -121,9 +122,9 @@ def max_uncompact_size(compacted_set, resolution) def compact(h3_set) h3_set = H3Indexes.with_contents(h3_set) out = H3Indexes.of_size(h3_set.size) - failure = Bindings::Private.compact(h3_set, out, out.size) - - raise "Couldn't compact given indexes" if failure + Bindings::Private.compactCells(h3_set, out, out.size).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end out.read end @@ -156,9 +157,10 @@ def uncompact(compacted_set, resolution) out = H3Indexes.of_size(max_size) h3_set = H3Indexes.with_contents(compacted_set) - failure = Bindings::Private.uncompact(h3_set, compacted_set.size, out, max_size, resolution) + Bindings::Private.uncompactCells(h3_set, compacted_set.size, out, max_size, resolution).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end - raise "Couldn't uncompact given indexes" if failure out.read end end diff --git a/lib/h3/indexing.rb b/lib/h3/indexing.rb index 53e9910..23399ed 100644 --- a/lib/h3/indexing.rb +++ b/lib/h3/indexing.rb @@ -29,10 +29,11 @@ def from_geo_coordinates(coords, resolution) raise(ArgumentError, "Invalid coordinates") end - coords = GeoCoord.new + coords = LatLng.new coords[:lat] = degs_to_rads(lat) coords[:lon] = degs_to_rads(lon) - Bindings::Private.geo_to_h3(coords, resolution) + + Bindings::Private.safe_call(:ulong_long, :geo_to_h3, coords, resolution) end # Derive coordinates for a given H3 index. @@ -47,8 +48,10 @@ def from_geo_coordinates(coords, resolution) # # @return [Array] A coordinate pair. def to_geo_coordinates(h3_index) - coords = GeoCoord.new - Bindings::Private.h3_to_geo(h3_index, coords) + coords = LatLng.new + Bindings::Private.h3_to_geo(h3_index, coords).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end [rads_to_degs(coords[:lat]), rads_to_degs(coords[:lon])] end @@ -71,7 +74,7 @@ def to_geo_coordinates(h3_index) # # @return [Array>] An array of six coordinate pairs. def to_boundary(h3_index) - geo_boundary = GeoBoundary.new + geo_boundary = CellBoundary.new Bindings::Private.h3_to_geo_boundary(h3_index, geo_boundary) geo_boundary[:verts].take(geo_boundary[:num_verts]).map do |d| [rads_to_degs(d[:lat]), rads_to_degs(d[:lon])] diff --git a/lib/h3/inspection.rb b/lib/h3/inspection.rb index f9c7f79..1ea6164 100644 --- a/lib/h3/inspection.rb +++ b/lib/h3/inspection.rb @@ -19,7 +19,7 @@ module Inspection # 9 # # @return [Integer] Resolution of H3 index - attach_function :resolution, :h3GetResolution, %i[h3_index], Resolution + attach_function :resolution, :getResolution, %i[h3_index], Resolution # @!method base_cell(h3_index) # @@ -32,7 +32,7 @@ module Inspection # 20 # # @return [Integer] Base cell number - attach_function :base_cell, :h3GetBaseCell, %i[h3_index], :int + attach_function :base_cell, :getBaseCellNumber, %i[h3_index], :int # @!method from_string(h3_string) # @@ -47,9 +47,9 @@ module Inspection # @raise [ArgumentError] If h3_string is nil # # @return [Integer] H3 index - def from_string(h3_string) - raise ArgumentError if h3_string.nil? - Bindings::Private.string_to_h3(h3_string) + def from_string(str) + raise ArgumentError if str.nil? + Bindings::Private.safe_call(:ulong_long, :from_string, str) end # @!method pentagon?(h3_index) @@ -63,7 +63,7 @@ def from_string(h3_string) # true # # @return [Boolean] True if the H3 index is a pentagon. - attach_predicate_function :pentagon?, :h3IsPentagon, %i[h3_index], :bool + attach_predicate_function :pentagon?, :isPentagon, %i[h3_index], :bool # @!method class_3_resolution?(h3_index) # @@ -77,7 +77,7 @@ def from_string(h3_string) # true # # @return [Boolean] True if the H3 index has a class III resolution. - attach_predicate_function :class_3_resolution?, :h3IsResClassIII, %i[h3_index], :bool + attach_predicate_function :class_3_resolution?, :isResClassIII, %i[h3_index], :bool # @!method valid?(h3_index) # @@ -90,7 +90,7 @@ def from_string(h3_string) # true # # @return [Boolean] True if the H3 index is valid. - attach_predicate_function :valid?, :h3IsValid, %i[h3_index], :bool + attach_predicate_function :valid?, :isValidCell, %i[h3_index], :bool # Derives the hexadecimal string representation for a given H3 index. # @@ -118,7 +118,9 @@ def to_string(h3_index) # 5 # # @return [Integer] Maximum possible number of faces - attach_function :max_face_count, :maxFaceCount, %i[h3_index], :int + def max_face_count(h3_index) + Bindings::Private.safe_call(:int, :max_face_count, h3_index) + end # Find all icosahedron faces intersected by a given H3 index. # @@ -132,9 +134,11 @@ def to_string(h3_index) def faces(h3_index) max_faces = max_face_count(h3_index) out = FFI::MemoryPointer.new(:int, max_faces) - Bindings::Private.h3_faces(h3_index, out) + Bindings::Private.h3_faces(h3_index, out).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end # The C function returns a sparse array whose holes are represented by -1. out.read_array_of_int(max_faces).reject(&:negative?).sort end end -end +end \ No newline at end of file diff --git a/lib/h3/miscellaneous.rb b/lib/h3/miscellaneous.rb index 47ec8bd..7cd149f 100644 --- a/lib/h3/miscellaneous.rb +++ b/lib/h3/miscellaneous.rb @@ -29,7 +29,9 @@ module Miscellaneous # 59.81085794 # # @return [Float] Length of edge in kilometres - attach_function :edge_length_km, :edgeLengthKm, [Resolution], :double + def edge_length_km(resolution) + Bindings::Private.safe_call(:double, :edge_length_km, resolution) + end # @!method edge_length_m(resolution) # @@ -42,7 +44,7 @@ module Miscellaneous # 3229.482772 # # @return [Float] Length of edge in metres - attach_function :edge_length_m, :edgeLengthM, [Resolution], :double + attach_function :edge_length_m, :distanceM, [Resolution], :double # @!method hex_area_km2(resolution) # @@ -55,7 +57,7 @@ module Miscellaneous # 252.9033645 # # @return [Float] Average hexagon area in square kilometres. - attach_function :hex_area_km2, :hexAreaKm2, [Resolution], :double + attach_function :hex_area_km2, :getHexagonAreaAvgKm2, [Resolution], :double # @!method hex_area_m2(resolution) # @@ -68,7 +70,7 @@ module Miscellaneous # 15047.5 # # @return [Float] Average hexagon area in square metres. - attach_function :hex_area_m2, :hexAreaM2, [Resolution], :double + attach_function :hex_area_m2, :getHexagonAreaAvgM2, [Resolution], :double # @!method hexagon_count(resolution) # @@ -81,7 +83,13 @@ module Miscellaneous # 14117882 # # @return [Integer] Number of unique hexagons - attach_function :hexagon_count, :numHexagons, [Resolution], :ulong_long + def hexagon_count(resolution) + out = FFI::MemoryPointer.new(:int64) + H3::Bindings::Private.hexagon_count(resolution, out).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end + out.read_int64 + end # @!method rads_to_degs(rads) # @@ -105,7 +113,7 @@ module Miscellaneous # 122 # # @return [Integer] The number of resolution 0 hexagons (base cells). - attach_function :base_cell_count, :res0IndexCount, [], :int + attach_function :base_cell_count, :res0CellCount, [], :int # @!method pentagon_count # @@ -117,7 +125,7 @@ module Miscellaneous # 12 # # @return [Integer] The number of pentagons per resolution. - attach_function :pentagon_count, :pentagonIndexCount, [], :int + attach_function :pentagon_count, :pentagonCount, [], :int # @!method cell_area_rads2 # diff --git a/lib/h3/traversal.rb b/lib/h3/traversal.rb index d0e6794..405a075 100644 --- a/lib/h3/traversal.rb +++ b/lib/h3/traversal.rb @@ -16,7 +16,7 @@ module Traversal # 91 # # @return [Integer] Maximum k-ring size. - attach_function :max_kring_size, :maxKringSize, %i[k_distance], :int + attach_function :max_kring_size, :maxGridDiskSize, %i[k_distance pointer], :h3_error_code # @!method distance(origin, h3_index) # @@ -30,7 +30,7 @@ module Traversal # 5 # # @return [Integer] Distance between indexes. - attach_function :distance, :h3Distance, %i[h3_index h3_index], :k_distance + attach_function :distance, :gridDistance, %i[h3_index h3_index], :k_distance # @!method line_size(origin, destination) # @@ -49,7 +49,7 @@ module Traversal # 6 # # @return [Integer] Number of hexagons found between indexes. - attach_function :line_size, :h3LineSize, %i[h3_index h3_index], :int + attach_function :line_size, :gridPathCellsSize, %i[h3_index h3_index], :int # Derives H3 indexes within k distance of the origin H3 index. # @@ -109,9 +109,14 @@ def hex_range(origin, k) # # @return [Array] Array of H3 indexes within the k-range. def k_ring(origin, k) - max_hexagons = max_kring_size(k) - out = H3Indexes.of_size(max_hexagons) - Bindings::Private.k_ring(origin, k, out) + out = FFI::MemoryPointer.new(:int64) + max_kring_size(k, out).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end + out = H3Indexes.of_size(out.read_int64) + Bindings::Private.k_ring(origin, k, out).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end out.read end diff --git a/lib/h3/unidirectional_edges.rb b/lib/h3/unidirectional_edges.rb index b3907a1..fef97ef 100644 --- a/lib/h3/unidirectional_edges.rb +++ b/lib/h3/unidirectional_edges.rb @@ -17,7 +17,7 @@ module UnidirectionalEdges # true # # @return [Boolean] True if indexes are neighbors - attach_predicate_function :neighbors?, :h3IndexesAreNeighbors, %i[h3_index h3_index], :bool + attach_predicate_function :neighbors?, :areNeighborCells, %i[h3_index h3_index], :bool # @!method unidirectional_edge_valid?(h3_index) # @@ -31,7 +31,7 @@ module UnidirectionalEdges # # @return [Boolean] True if H3 index is a valid unidirectional edge attach_predicate_function :unidirectional_edge_valid?, - :h3UnidirectionalEdgeIsValid, + :isValidDirectedEdge, %i[h3_index], :bool @@ -48,7 +48,7 @@ module UnidirectionalEdges # # @return [Integer] H3 edge index attach_function :unidirectional_edge, - :getH3UnidirectionalEdge, + :cellsToDirectedEdge, %i[h3_index h3_index], :h3_index @@ -64,7 +64,7 @@ module UnidirectionalEdges # # @return [Integer] H3 index attach_function :destination_from_unidirectional_edge, - :getDestinationH3IndexFromUnidirectionalEdge, + :getDirectedEdgeDestination, %i[h3_index], :h3_index @@ -80,7 +80,7 @@ module UnidirectionalEdges # # @return [Integer] H3 index attach_function :origin_from_unidirectional_edge, - :getOriginH3IndexFromUnidirectionalEdge, + :getDirectedEdgeOrigin, %i[h3_index], :h3_index @@ -137,7 +137,7 @@ def unidirectional_edges_from_hexagon(origin) # # @return [Array>] Edge boundary coordinates for a hexagon def unidirectional_edge_boundary(edge) - geo_boundary = GeoBoundary.new + geo_boundary = CellBoundary.new Bindings::Private.h3_unidirectional_edge_boundary(edge, geo_boundary) geo_boundary[:verts].take(geo_boundary[:num_verts]).map do |d| [rads_to_degs(d[:lat]), rads_to_degs(d[:lon])] diff --git a/lib/h3/version.rb b/lib/h3/version.rb index 460c097..d6bf47a 100644 --- a/lib/h3/version.rb +++ b/lib/h3/version.rb @@ -1,3 +1,3 @@ module H3 - VERSION = "3.7.4".freeze + VERSION = "4.0.0".freeze end diff --git a/spec/hierarchy_spec.rb b/spec/hierarchy_spec.rb index 9f29541..ab2b854 100644 --- a/spec/hierarchy_spec.rb +++ b/spec/hierarchy_spec.rb @@ -16,15 +16,6 @@ subject(:children) { H3.children(h3_index, child_resolution) } - context "when resolution is 3" do - let(:child_resolution) { 3 } - let(:count) { 0 } - - it "has 0 children" do - expect(children.count).to eq count - end - end - context "when resolution is 9" do let(:child_resolution) { 9 } let(:count) { 1 } @@ -57,11 +48,19 @@ end end + context "when resolution is 3" do + let(:child_resolution) { 3 } + + it "raises an error" do + expect { children }.to raise_error(H3::Bindings::Error::ResolutionDomainError) + end + end + context "when the resolution is -1" do let(:child_resolution) { -1 } it "raises an error" do - expect { children }.to raise_error(ArgumentError) + expect { children }.to raise_error(H3::Bindings::Error::ResolutionDomainError) end end @@ -69,7 +68,7 @@ let(:child_resolution) { 16 } it "raises an error" do - expect { children }.to raise_error(ArgumentError) + expect { children }.to raise_error(H3::Bindings::Error::ResolutionDomainError) end end end @@ -79,13 +78,6 @@ subject(:max_children) { H3.max_children(h3_index, child_resolution) } - context "when resolution is 3" do - let(:child_resolution) { 3 } - let(:count) { 0 } - - it { is_expected.to eq(count) } - end - context "when resolution is 9" do let(:child_resolution) { 9 } let(:count) { 1 } @@ -106,6 +98,30 @@ it { is_expected.to eq(count) } end + + context "when resolution is 3" do + let(:child_resolution) { 3 } + + it "raises an error" do + expect { max_children }.to raise_error(H3::Bindings::Error::ResolutionDomainError) + end + end + + context "when the resolution is -1" do + let(:child_resolution) { -1 } + + it "raises an error" do + expect { max_children }.to raise_error(H3::Bindings::Error::ResolutionDomainError) + end + end + + context "when the resolution is 16" do + let(:child_resolution) { 16 } + + it "raises an error" do + expect { max_children }.to raise_error(H3::Bindings::Error::ResolutionDomainError) + end + end end describe ".compact" do @@ -150,7 +166,7 @@ let(:resolution) { 8 } it "raises error" do - expect { uncompact }.to raise_error(ArgumentError) + expect { uncompact }.to raise_error(H3::Bindings::Error::ResolutionMismatchError) end end end @@ -168,7 +184,7 @@ let(:resolution) { 8 } it "raises an error" do - expect { max_uncompact_size }.to raise_error(ArgumentError) + expect { max_uncompact_size }.to raise_error(H3::Bindings::Error::ResolutionMismatchError) end end end diff --git a/spec/indexing_spec.rb b/spec/indexing_spec.rb index 930445a..8be84e8 100644 --- a/spec/indexing_spec.rb +++ b/spec/indexing_spec.rb @@ -1,3 +1,5 @@ +require "spec_helper" + RSpec.describe H3 do include_context "constants" diff --git a/spec/miscellaneous_spec.rb b/spec/miscellaneous_spec.rb index def6473..f423cb7 100644 --- a/spec/miscellaneous_spec.rb +++ b/spec/miscellaneous_spec.rb @@ -14,7 +14,7 @@ let(:result) { false } it "returns the expected result" do - expect { hexagon_count }.to raise_error(ArgumentError) + expect { hexagon_count }.to raise_error(H3::Bindings::Error::ResolutionDomainError) end end end @@ -39,7 +39,7 @@ describe ".hex_area_km2" do let(:resolution) { 2 } - let(:result) { 86745.85403 } + let(:result) { 86801.7803989972 } subject(:hex_area_km2) { H3.hex_area_km2(resolution) } @@ -48,7 +48,7 @@ describe ".hex_area_m2" do let(:resolution) { 2 } - let(:result) { 86745854035.0 } + let(:result) { 86801780398.99731 } subject(:hex_area_m2) { H3.hex_area_m2(resolution) } @@ -64,14 +64,14 @@ it { is_expected.to eq(result) } end - describe ".edge_length_m" do - let(:resolution) { 2 } - let(:result) { 158244.6558 } + # describe ".edge_length_m" do + # let(:resolution) { 2 } + # let(:result) { 158244.6558 } - subject(:edge_length_m) { H3.edge_length_m(resolution) } + # subject(:edge_length_m) { H3.edge_length_m(resolution) } - it { is_expected.to eq(result) } - end + # it { is_expected.to eq(result) } + # end describe ".base_cells" do let(:count) { 122 } @@ -82,148 +82,148 @@ end end - describe ".pentagon_count" do - let(:count) { 12 } - subject(:pentagon_count) { H3.pentagon_count } - - it "has 12 pentagons per resolution" do - expect(pentagon_count).to eq(count) - end - end - - describe ".pentagons" do - let(:resolution) { 4 } - let(:expected) do - [ - 594615896891195391, 594967740612083711, - 595319584332972031, 595812165542215679, - 596199193635192831, 596515852983992319, - 596691774844436479, 597008434193235967, - 597395462286213119, 597888043495456767, - 598239887216345087, 598591730937233407 - ] - end - subject(:pentagons) { H3.pentagons(resolution) } - - it "returns pentagons at the given resolution" do - expect(pentagons).to eq(expected) - end - end - - describe ".cell_area_rads2" do - let(:cell) { "8928308280fffff".to_i(16) } - let(:expected) { 2.6952182709835757e-09 } - subject(:cell_area_rads2) { H3.cell_area_rads2(cell) } - - it "returns cell area in rads2" do - expect(cell_area_rads2).to be_within(0.0001).of(expected) - end - end - - describe ".cell_area_km2" do - let(:cell) { "8928308280fffff".to_i(16) } - let(:expected) { 0.10939818864648902 } - subject(:cell_area_km2) { H3.cell_area_km2(cell) } - - it "returns cell area in km2" do - expect(cell_area_km2).to be_within(0.0001).of(expected) - end - end - - describe ".cell_area_m2" do - let(:cell) { "8928308280fffff".to_i(16) } - let(:expected) { 109398.18864648901 } - subject(:cell_area_m2) { H3.cell_area_m2(cell) } - - it "returns cell area in m2" do - expect(cell_area_m2).to be_within(0.0001).of(expected) - end - end - - describe ".exact_edge_length_rads" do - let(:cell) { "11928308280fffff".to_i(16) } - let(:expected) { 3.287684056071637e-05 } - subject(:exact_edge_length_rads) { H3.exact_edge_length_rads(cell) } - - it "returns edge length in rads" do - expect(exact_edge_length_rads).to be_within(0.0001).of(expected) - end - end - - describe ".exact_edge_length_km" do - let(:cell) { "11928308280fffff".to_i(16) } - let(:expected) { 0.20945858729823577 } - subject(:exact_edge_length_km) { H3.exact_edge_length_km(cell) } - - it "returns edge length in km" do - expect(exact_edge_length_km).to be_within(0.0001).of(expected) - end - end - - describe ".exact_edge_length_m" do - let(:cell) { "11928308280fffff".to_i(16) } - let(:expected) { 209.45858729823578 } - subject(:exact_edge_length_m) { H3.exact_edge_length_m(cell) } - - it "returns edge length in m" do - expect(exact_edge_length_m).to be_within(0.0001).of(expected) - end - end - - describe ".point_distance_rads" do - let(:a) { [41.3964809, 2.160444] } - let(:b) { [41.3870609, 2.164917] } - let(:expected) { 0.00017453024784008713 } - subject(:point_distance_rads) { H3.point_distance_rads(a, b) } - - it "returns distance between points in rads" do - expect(point_distance_rads).to be_within(0.0001).of(expected) - end - - context "when the coordinates are invalid" do - let(:a) { [91, -18] } - - it "raises an argument error" do - expect { point_distance_rads }.to raise_error(ArgumentError) - end - end - end - - describe ".point_distance_km" do - let(:a) { [41.3964809, 2.160444] } - let(:b) { [41.3870609, 2.164917] } - let(:expected) { 1.1119334622766763 } - subject(:point_distance_km) { H3.point_distance_km(a, b) } - - it "returns distance between points in km" do - expect(point_distance_km).to be_within(0.0001).of(expected) - end - - context "when the coordinates are invalid" do - let(:a) { [89, -181] } - - it "raises an argument error" do - expect { point_distance_km }.to raise_error(ArgumentError) - end - end - end - - describe ".point_distance_m" do - let(:a) { [41.3964809, 2.160444] } - let(:b) { [41.3870609, 2.164917] } - let(:expected) { 1111.9334622766764 } - subject(:point_distance_m) { H3.point_distance_m(a, b) } - - it "returns distance between points in m" do - expect(point_distance_m).to be_within(0.0001).of(expected) - end - - context "when the coordinates are invalid" do - let(:a) { "boom" } - - it "raises an argument error" do - expect { point_distance_m }.to raise_error(ArgumentError) - end - end - end + # describe ".pentagon_count" do + # let(:count) { 12 } + # subject(:pentagon_count) { H3.pentagon_count } + + # it "has 12 pentagons per resolution" do + # expect(pentagon_count).to eq(count) + # end + # end + + # describe ".pentagons" do + # let(:resolution) { 4 } + # let(:expected) do + # [ + # 594615896891195391, 594967740612083711, + # 595319584332972031, 595812165542215679, + # 596199193635192831, 596515852983992319, + # 596691774844436479, 597008434193235967, + # 597395462286213119, 597888043495456767, + # 598239887216345087, 598591730937233407 + # ] + # end + # subject(:pentagons) { H3.pentagons(resolution) } + + # it "returns pentagons at the given resolution" do + # expect(pentagons).to eq(expected) + # end + # end + + # describe ".cell_area_rads2" do + # let(:cell) { "8928308280fffff".to_i(16) } + # let(:expected) { 2.6952182709835757e-09 } + # subject(:cell_area_rads2) { H3.cell_area_rads2(cell) } + + # it "returns cell area in rads2" do + # expect(cell_area_rads2).to be_within(0.0001).of(expected) + # end + # end + + # describe ".cell_area_km2" do + # let(:cell) { "8928308280fffff".to_i(16) } + # let(:expected) { 0.10939818864648902 } + # subject(:cell_area_km2) { H3.cell_area_km2(cell) } + + # it "returns cell area in km2" do + # expect(cell_area_km2).to be_within(0.0001).of(expected) + # end + # end + + # describe ".cell_area_m2" do + # let(:cell) { "8928308280fffff".to_i(16) } + # let(:expected) { 109398.18864648901 } + # subject(:cell_area_m2) { H3.cell_area_m2(cell) } + + # it "returns cell area in m2" do + # expect(cell_area_m2).to be_within(0.0001).of(expected) + # end + # end + + # describe ".exact_edge_length_rads" do + # let(:cell) { "11928308280fffff".to_i(16) } + # let(:expected) { 3.287684056071637e-05 } + # subject(:exact_edge_length_rads) { H3.exact_edge_length_rads(cell) } + + # it "returns edge length in rads" do + # expect(exact_edge_length_rads).to be_within(0.0001).of(expected) + # end + # end + + # describe ".exact_edge_length_km" do + # let(:cell) { "11928308280fffff".to_i(16) } + # let(:expected) { 0.20945858729823577 } + # subject(:exact_edge_length_km) { H3.exact_edge_length_km(cell) } + + # it "returns edge length in km" do + # expect(exact_edge_length_km).to be_within(0.0001).of(expected) + # end + # end + + # describe ".exact_edge_length_m" do + # let(:cell) { "11928308280fffff".to_i(16) } + # let(:expected) { 209.45858729823578 } + # subject(:exact_edge_length_m) { H3.exact_edge_length_m(cell) } + + # it "returns edge length in m" do + # expect(exact_edge_length_m).to be_within(0.0001).of(expected) + # end + # end + + # describe ".point_distance_rads" do + # let(:a) { [41.3964809, 2.160444] } + # let(:b) { [41.3870609, 2.164917] } + # let(:expected) { 0.00017453024784008713 } + # subject(:point_distance_rads) { H3.point_distance_rads(a, b) } + + # it "returns distance between points in rads" do + # expect(point_distance_rads).to be_within(0.0001).of(expected) + # end + + # context "when the coordinates are invalid" do + # let(:a) { [91, -18] } + + # it "raises an argument error" do + # expect { point_distance_rads }.to raise_error(ArgumentError) + # end + # end + # end + + # describe ".point_distance_km" do + # let(:a) { [41.3964809, 2.160444] } + # let(:b) { [41.3870609, 2.164917] } + # let(:expected) { 1.1119334622766763 } + # subject(:point_distance_km) { H3.point_distance_km(a, b) } + + # it "returns distance between points in km" do + # expect(point_distance_km).to be_within(0.0001).of(expected) + # end + + # context "when the coordinates are invalid" do + # let(:a) { [89, -181] } + + # it "raises an argument error" do + # expect { point_distance_km }.to raise_error(ArgumentError) + # end + # end + # end + + # describe ".point_distance_m" do + # let(:a) { [41.3964809, 2.160444] } + # let(:b) { [41.3870609, 2.164917] } + # let(:expected) { 1111.9334622766764 } + # subject(:point_distance_m) { H3.point_distance_m(a, b) } + + # it "returns distance between points in m" do + # expect(point_distance_m).to be_within(0.0001).of(expected) + # end + + # context "when the coordinates are invalid" do + # let(:a) { "boom" } + + # it "raises an argument error" do + # expect { point_distance_m }.to raise_error(ArgumentError) + # end + # end + # end end diff --git a/spec/regions_spec.rb b/spec/regions_spec.rb index 2c164ba..c0a0bc4 100644 --- a/spec/regions_spec.rb +++ b/spec/regions_spec.rb @@ -1,71 +1,71 @@ RSpec.describe H3 do - include_context "constants" + # include_context "constants" - describe ".polyfill" do - let(:geojson) do - File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury_without_holes.json")) - end - let(:resolution) { 9 } - let(:expected_count) { 14_369 } + # describe ".polyfill" do + # let(:geojson) do + # File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury_without_holes.json")) + # end + # let(:resolution) { 9 } + # let(:expected_count) { 14_369 } - subject(:polyfill) { H3.polyfill(geojson, resolution) } + # subject(:polyfill) { H3.polyfill(geojson, resolution) } - it "has the correct number of hexagons" do - expect(polyfill.count).to eq expected_count - end + # it "has the correct number of hexagons" do + # expect(polyfill.count).to eq expected_count + # end - context "when banbury area has two holes in it" do - let(:geojson) do - File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json")) - end - let(:expected_count) { 13_526 } + # context "when banbury area has two holes in it" do + # let(:geojson) do + # File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json")) + # end + # let(:expected_count) { 13_526 } - it "has fewer hexagons" do - expect(polyfill.count).to eq expected_count - end - end + # it "has fewer hexagons" do + # expect(polyfill.count).to eq expected_count + # end + # end - context "when polyfilling australia" do - let(:geojson) do - File.read(File.join(File.dirname(__FILE__), "support/fixtures/australia.json")) - end - let(:expect_count) { 92 } + # context "when polyfilling australia" do + # let(:geojson) do + # File.read(File.join(File.dirname(__FILE__), "support/fixtures/australia.json")) + # end + # let(:expect_count) { 92 } - it "has the correct number of hexagons" do - expect(polyfill.count).to eq expect_count - end - end - end + # it "has the correct number of hexagons" do + # expect(polyfill.count).to eq expect_count + # end + # end + # end - describe ".max_polyfill_size" do - let(:geojson) do - File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json")) - end - let(:resolution) { 9 } - let(:expected_count) { 47_018 } + # describe ".max_polyfill_size" do + # let(:geojson) do + # File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json")) + # end + # let(:resolution) { 9 } + # let(:expected_count) { 47_018 } - subject(:max_polyfill_size) { H3.max_polyfill_size(geojson, resolution) } + # subject(:max_polyfill_size) { H3.max_polyfill_size(geojson, resolution) } - it "has the correct number of hexagons" do - expect(max_polyfill_size).to eq expected_count - end - end + # it "has the correct number of hexagons" do + # expect(max_polyfill_size).to eq expected_count + # end + # end - describe ".h3_set_to_linked_geo" do - let(:geojson) do - File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json")) - end - let(:resolution) { 8 } - let(:hexagons) { H3.polyfill(geojson, resolution) } + # describe ".h3_set_to_linked_geo" do + # let(:geojson) do + # File.read(File.join(File.dirname(__FILE__), "support/fixtures/banbury.json")) + # end + # let(:resolution) { 8 } + # let(:hexagons) { H3.polyfill(geojson, resolution) } - subject(:h3_set_to_linked_geo) { H3.h3_set_to_linked_geo(hexagons) } + # subject(:h3_set_to_linked_geo) { H3.h3_set_to_linked_geo(hexagons) } - it "has 3 outlines" do - h3_set_to_linked_geo.count == 3 - end + # it "has 3 outlines" do + # h3_set_to_linked_geo.count == 3 + # end - it "can be converted to GeoJSON" do - expect(H3.coordinates_to_geo_json(h3_set_to_linked_geo)).to be_truthy - end - end + # it "can be converted to GeoJSON" do + # expect(H3.coordinates_to_geo_json(h3_set_to_linked_geo)).to be_truthy + # end + # end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f355809..216b035 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +require "pry" + # require "coveralls" # Coveralls.wear! diff --git a/spec/traversal_spec.rb b/spec/traversal_spec.rb index 7deece1..f3235c6 100644 --- a/spec/traversal_spec.rb +++ b/spec/traversal_spec.rb @@ -1,368 +1,368 @@ RSpec.describe H3 do - include_context "constants" - - describe ".k_ring" do - let(:h3_index) { "8928308280fffff".to_i(16) } - - subject(:k_ring) { H3.k_ring(h3_index, k) } - - context "when k range is 1" do - let(:k) { 1 } - let(:count) { 7 } - let(:expected) do - %w(8928308280fffff 8928308280bffff 89283082873ffff 89283082877ffff - 8928308283bffff 89283082807ffff 89283082803ffff).map { |i| i.to_i(16) } - end - - it "has 7 hexagons" do - expect(k_ring.count).to eq count - end - - it "has the expected hexagons" do - expect(k_ring).to eq expected - end - end - - context "when k range is 2" do - let(:k) { 2 } - let(:count) { 19 } - - it "has 19 hexagons" do - expect(k_ring.count).to eq count - end - end - - context "when k range is 10" do - let(:k) { 10 } - let(:count) { 331 } - - it "has 331 hexagons" do - expect(k_ring.count).to eq count - end - end - end - - describe ".max_kring_size" do - let(:k) { 2 } - let(:result) { 19 } - - subject(:max_kring_size) { H3.max_kring_size(k) } - - it { is_expected.to eq(result) } - - context "when provided with a bad k value" do - let(:k) { "boom" } - - it "raises an error" do - expect { max_kring_size }.to raise_error(TypeError) - end - end - - context "when given a k too large" do - let(:k) { too_long_number } - - it "raises an error" do - expect { max_kring_size }.to raise_error(RangeError) - end - end - end - - describe ".k_ring_distances" do - let(:h3_index) { "8928308280fffff".to_i(16) } - let(:k) { 1 } - let(:outer_ring) do - [ - "8928308280bffff", "89283082873ffff", "89283082877ffff", - "8928308283bffff", "89283082807ffff", "89283082803ffff" - ].map { |i| i.to_i(16) } - end - - subject(:k_ring_distances) { H3.k_ring_distances(h3_index, k) } - - it "has two ring sets" do - expect(k_ring_distances.count).to eq 2 - end - - it "has an inner ring containing hexagons of distance 0" do - expect(k_ring_distances[0]).to eq [h3_index] - end - - it "has an outer ring containing hexagons of distance 1" do - expect(k_ring_distances[1].count).to eq 6 - end - - it "has an outer ring containing all expected indexes" do - k_ring_distances[1].each do |index| - expect(outer_ring).to include(index) - end - end - end - - describe ".hex_range" do - let(:h3_index) { "8928308280fffff".to_i(16) } - - subject(:hex_range) { H3.hex_range(h3_index, k) } - - context "when k range is 1" do - let(:k) { 1 } - let(:count) { 7 } - let(:expected) do - %w(8928308280fffff 8928308280bffff 89283082873ffff 89283082877ffff - 8928308283bffff 89283082807ffff 89283082803ffff).map { |i| i.to_i(16) } - end - - it "has 7 hexagons" do - expect(hex_range.count).to eq count - end - - it "has the expected hexagons" do - expect(hex_range).to eq expected - end - end - - context "when k range is 2" do - let(:k) { 2 } - let(:count) { 19 } - - it "has 19 hexagons" do - expect(hex_range.count).to eq count - end - end - - context "when k range is 10" do - let(:k) { 10 } - let(:count) { 331 } - - it "has 331 hexagons" do - expect(hex_range.count).to eq count - end - end - - context "when range contains a pentagon" do - let(:h3_index) { "821c07fffffffff".to_i(16) } - let(:k) { 1 } - - it "raises an error" do - expect { hex_range }.to raise_error(ArgumentError) - end - end - end - - describe ".hex_range_distances" do - let(:h3_index) { "85283473fffffff".to_i(16) } - let(:k) { 1 } - let(:outer_ring) do - [ - "85283447fffffff", "8528347bfffffff", "85283463fffffff", - "85283477fffffff", "8528340ffffffff", "8528340bfffffff" - ].map { |i| i.to_i(16) } - end - - subject(:hex_range_distances) { H3.hex_range_distances(h3_index, k) } - - it "has two range sets" do - expect(hex_range_distances.count).to eq 2 - end - - it "has an inner range containing hexagons of distance 0" do - expect(hex_range_distances[0]).to eq [h3_index] - end - - it "has an outer range containing hexagons of distance 1" do - expect(hex_range_distances[1].count).to eq 6 - end - - it "has an outer range containing all expected indexes" do - hex_range_distances[1].each do |index| - expect(outer_ring).to include(index) - end - end - - context "when there is pentagonal distortion" do - let(:h3_index) { "821c07fffffffff".to_i(16) } - - it "raises an error" do - expect { hex_range_distances }.to raise_error(ArgumentError) - end - end - end - - describe ".hex_ranges" do - let(:h3_index) { "8928308280fffff".to_i(16) } - let(:h3_set) { [h3_index] } - let(:k) { 1 } - let(:outer_ring) do - [ - "8928308280bffff", "89283082807ffff", "89283082877ffff", - "89283082803ffff", "89283082873ffff", "8928308283bffff" - ].map { |i| i.to_i(16) } - end - - subject(:hex_ranges) { H3.hex_ranges(h3_set, k) } - - it "contains a single k/v pair" do - expect(hex_ranges.count).to eq 1 - end - - it "has one key, the h3_index" do - expect(hex_ranges.keys.first).to eq h3_index - end - - it "has two ring sets" do - expect(hex_ranges[h3_index].count).to eq 2 - end - - it "has an inner ring containing only the original index" do - expect(hex_ranges[h3_index].first).to eq [h3_index] - end - - it "has an outer ring containing six indexes" do - expect(hex_ranges[h3_index].last.count).to eq 6 - end - - it "has an outer ring containing all expected indexes" do - hex_ranges[h3_index].last.each do |index| - expect(outer_ring).to include(index) - end - end - - context "when there is pentagonal distortion" do - let(:h3_index) { "821c07fffffffff".to_i(16) } - - it "raises an error" do - expect { hex_ranges }.to raise_error(ArgumentError) - end - end - - context "when k is 2" do - let(:k) { 2 } - - it "contains 3 rings" do - expect(hex_ranges[h3_index].count).to eq 3 - end - - it "has an inner ring of size 1" do - expect(hex_ranges[h3_index][0].count).to eq 1 - end - - it "has a middle ring of size 6" do - expect(hex_ranges[h3_index][1].count).to eq 6 - end - - it "has an outer ring of size 12" do - expect(hex_ranges[h3_index][2].count).to eq 12 - end - end - - context "when run without grouping" do - let(:hex_array) do - [ - 617700169958293503, 617700169958031359, 617700169964847103, - 617700169965109247, 617700169961177087, 617700169957769215, - 617700169957507071 - ] - end - - subject(:hex_ranges) { H3.hex_ranges(h3_set, k, grouped: false) } - - it { is_expected.to eq hex_array } - end - - context "when compared with the ungrouped version" do - let(:h3_index2) { "8f19425b6ccd582".to_i(16) } - let(:h3_index3) { "89283082873ffff".to_i(16) } - let(:h3_set) { [h3_index, h3_index2, h3_index3]} - let(:ungrouped) { H3.hex_ranges(h3_set, k, grouped: false) } - let(:k) { 3 } - - it "has the same elements when we remove grouping" do - expect(hex_ranges.values.flatten).to eq(ungrouped) - end - end - end - - describe ".hex_ring" do - let(:h3_index) { "8928308280fffff".to_i(16) } - - subject(:hex_ring) { H3.hex_ring(h3_index, k) } - - context "when k range is 1" do - let(:k) { 1 } - let(:count) { 6 } - let(:expected) do - %w(89283082803ffff 8928308280bffff 89283082873ffff 89283082877ffff - 8928308283bffff 89283082807ffff).map { |i| i.to_i(16) } - end - - it "has 6 hexagons" do - expect(hex_ring.count).to eq count - end - - it "has the expected hexagons" do - expect(hex_ring).to eq expected - end - end - - context "when k range is 2" do - let(:k) { 2 } - let(:count) { 12 } - - it "has 12 hexagons" do - expect(hex_ring.count).to eq count - end - end - - context "when k range is 10" do - let(:k) { 10 } - let(:count) { 60 } - - it "has 60 hexagons" do - expect(hex_ring.count).to eq count - end - end - - context "when the ring contains a pentagon" do - let(:h3_index) { "821c07fffffffff".to_i(16) } - let(:k) { 1 } - - it "raises an error" do - expect { hex_ring }.to raise_error(ArgumentError) - end - end - end - - describe ".distance" do - let(:origin) { "89283082993ffff".to_i(16) } - let(:destination) { "89283082827ffff".to_i(16) } - let(:result) { 5 } - - subject(:distance) { H3.distance(origin, destination) } - - it { is_expected.to eq(result) } - end - - describe ".line_size" do - let(:origin) { "89283082993ffff".to_i(16) } - let(:destination) { "89283082827ffff".to_i(16) } - let(:result) { 6 } - - subject(:h3_line_size) { H3.line_size(origin, destination) } - - it { is_expected.to eq(result) } - end - - describe ".line" do - let(:origin) { "89283082993ffff".to_i(16) } - let(:destination) { "89283082827ffff".to_i(16) } - let(:result) do - [ - "89283082993ffff", "8928308299bffff", "892830829d7ffff", - "892830829c3ffff", "892830829cbffff", "89283082827ffff" - ].map { |i| i.to_i(16) } - end - - subject(:line) { H3.line(origin, destination) } - - it { is_expected.to eq(result) } - end +# include_context "constants" + +# describe ".k_ring" do +# let(:h3_index) { "8928308280fffff".to_i(16) } + +# subject(:k_ring) { H3.k_ring(h3_index, k) } + +# context "when k range is 1" do +# let(:k) { 1 } +# let(:count) { 7 } +# let(:expected) do +# %w(8928308280fffff 8928308280bffff 89283082873ffff 89283082877ffff +# 8928308283bffff 89283082807ffff 89283082803ffff).map { |i| i.to_i(16) } +# end + +# it "has 7 hexagons" do +# expect(k_ring.count).to eq count +# end + +# it "has the expected hexagons" do +# expect(k_ring).to eq expected +# end +# end + +# context "when k range is 2" do +# let(:k) { 2 } +# let(:count) { 19 } + +# it "has 19 hexagons" do +# expect(k_ring.count).to eq count +# end +# end + +# context "when k range is 10" do +# let(:k) { 10 } +# let(:count) { 331 } + +# it "has 331 hexagons" do +# expect(k_ring.count).to eq count +# end +# end +# end + +# describe ".max_kring_size" do +# let(:k) { 2 } +# let(:result) { 19 } + +# subject(:max_kring_size) { H3.max_kring_size(k) } + +# it { is_expected.to eq(result) } + +# context "when provided with a bad k value" do +# let(:k) { "boom" } + +# it "raises an error" do +# expect { max_kring_size }.to raise_error(TypeError) +# end +# end + +# context "when given a k too large" do +# let(:k) { too_long_number } + +# it "raises an error" do +# expect { max_kring_size }.to raise_error(RangeError) +# end +# end +# end + +# describe ".k_ring_distances" do +# let(:h3_index) { "8928308280fffff".to_i(16) } +# let(:k) { 1 } +# let(:outer_ring) do +# [ +# "8928308280bffff", "89283082873ffff", "89283082877ffff", +# "8928308283bffff", "89283082807ffff", "89283082803ffff" +# ].map { |i| i.to_i(16) } +# end + +# subject(:k_ring_distances) { H3.k_ring_distances(h3_index, k) } + +# it "has two ring sets" do +# expect(k_ring_distances.count).to eq 2 +# end + +# it "has an inner ring containing hexagons of distance 0" do +# expect(k_ring_distances[0]).to eq [h3_index] +# end + +# it "has an outer ring containing hexagons of distance 1" do +# expect(k_ring_distances[1].count).to eq 6 +# end + +# it "has an outer ring containing all expected indexes" do +# k_ring_distances[1].each do |index| +# expect(outer_ring).to include(index) +# end +# end +# end + +# describe ".hex_range" do +# let(:h3_index) { "8928308280fffff".to_i(16) } + +# subject(:hex_range) { H3.hex_range(h3_index, k) } + +# context "when k range is 1" do +# let(:k) { 1 } +# let(:count) { 7 } +# let(:expected) do +# %w(8928308280fffff 8928308280bffff 89283082873ffff 89283082877ffff +# 8928308283bffff 89283082807ffff 89283082803ffff).map { |i| i.to_i(16) } +# end + +# it "has 7 hexagons" do +# expect(hex_range.count).to eq count +# end + +# it "has the expected hexagons" do +# expect(hex_range).to eq expected +# end +# end + +# context "when k range is 2" do +# let(:k) { 2 } +# let(:count) { 19 } + +# it "has 19 hexagons" do +# expect(hex_range.count).to eq count +# end +# end + +# context "when k range is 10" do +# let(:k) { 10 } +# let(:count) { 331 } + +# it "has 331 hexagons" do +# expect(hex_range.count).to eq count +# end +# end + +# context "when range contains a pentagon" do +# let(:h3_index) { "821c07fffffffff".to_i(16) } +# let(:k) { 1 } + +# it "raises an error" do +# expect { hex_range }.to raise_error(ArgumentError) +# end +# end +# end + +# describe ".hex_range_distances" do +# let(:h3_index) { "85283473fffffff".to_i(16) } +# let(:k) { 1 } +# let(:outer_ring) do +# [ +# "85283447fffffff", "8528347bfffffff", "85283463fffffff", +# "85283477fffffff", "8528340ffffffff", "8528340bfffffff" +# ].map { |i| i.to_i(16) } +# end + +# subject(:hex_range_distances) { H3.hex_range_distances(h3_index, k) } + +# it "has two range sets" do +# expect(hex_range_distances.count).to eq 2 +# end + +# it "has an inner range containing hexagons of distance 0" do +# expect(hex_range_distances[0]).to eq [h3_index] +# end + +# it "has an outer range containing hexagons of distance 1" do +# expect(hex_range_distances[1].count).to eq 6 +# end + +# it "has an outer range containing all expected indexes" do +# hex_range_distances[1].each do |index| +# expect(outer_ring).to include(index) +# end +# end + +# context "when there is pentagonal distortion" do +# let(:h3_index) { "821c07fffffffff".to_i(16) } + +# it "raises an error" do +# expect { hex_range_distances }.to raise_error(ArgumentError) +# end +# end +# end + +# describe ".hex_ranges" do +# let(:h3_index) { "8928308280fffff".to_i(16) } +# let(:h3_set) { [h3_index] } +# let(:k) { 1 } +# let(:outer_ring) do +# [ +# "8928308280bffff", "89283082807ffff", "89283082877ffff", +# "89283082803ffff", "89283082873ffff", "8928308283bffff" +# ].map { |i| i.to_i(16) } +# end + +# subject(:hex_ranges) { H3.hex_ranges(h3_set, k) } + +# it "contains a single k/v pair" do +# expect(hex_ranges.count).to eq 1 +# end + +# it "has one key, the h3_index" do +# expect(hex_ranges.keys.first).to eq h3_index +# end + +# it "has two ring sets" do +# expect(hex_ranges[h3_index].count).to eq 2 +# end + +# it "has an inner ring containing only the original index" do +# expect(hex_ranges[h3_index].first).to eq [h3_index] +# end + +# it "has an outer ring containing six indexes" do +# expect(hex_ranges[h3_index].last.count).to eq 6 +# end + +# it "has an outer ring containing all expected indexes" do +# hex_ranges[h3_index].last.each do |index| +# expect(outer_ring).to include(index) +# end +# end + +# context "when there is pentagonal distortion" do +# let(:h3_index) { "821c07fffffffff".to_i(16) } + +# it "raises an error" do +# expect { hex_ranges }.to raise_error(ArgumentError) +# end +# end + +# context "when k is 2" do +# let(:k) { 2 } + +# it "contains 3 rings" do +# expect(hex_ranges[h3_index].count).to eq 3 +# end + +# it "has an inner ring of size 1" do +# expect(hex_ranges[h3_index][0].count).to eq 1 +# end + +# it "has a middle ring of size 6" do +# expect(hex_ranges[h3_index][1].count).to eq 6 +# end + +# it "has an outer ring of size 12" do +# expect(hex_ranges[h3_index][2].count).to eq 12 +# end +# end + +# context "when run without grouping" do +# let(:hex_array) do +# [ +# 617700169958293503, 617700169958031359, 617700169964847103, +# 617700169965109247, 617700169961177087, 617700169957769215, +# 617700169957507071 +# ] +# end + +# subject(:hex_ranges) { H3.hex_ranges(h3_set, k, grouped: false) } + +# it { is_expected.to eq hex_array } +# end + +# context "when compared with the ungrouped version" do +# let(:h3_index2) { "8f19425b6ccd582".to_i(16) } +# let(:h3_index3) { "89283082873ffff".to_i(16) } +# let(:h3_set) { [h3_index, h3_index2, h3_index3]} +# let(:ungrouped) { H3.hex_ranges(h3_set, k, grouped: false) } +# let(:k) { 3 } + +# it "has the same elements when we remove grouping" do +# expect(hex_ranges.values.flatten).to eq(ungrouped) +# end +# end +# end + +# describe ".hex_ring" do +# let(:h3_index) { "8928308280fffff".to_i(16) } + +# subject(:hex_ring) { H3.hex_ring(h3_index, k) } + +# context "when k range is 1" do +# let(:k) { 1 } +# let(:count) { 6 } +# let(:expected) do +# %w(89283082803ffff 8928308280bffff 89283082873ffff 89283082877ffff +# 8928308283bffff 89283082807ffff).map { |i| i.to_i(16) } +# end + +# it "has 6 hexagons" do +# expect(hex_ring.count).to eq count +# end + +# it "has the expected hexagons" do +# expect(hex_ring).to eq expected +# end +# end + +# context "when k range is 2" do +# let(:k) { 2 } +# let(:count) { 12 } + +# it "has 12 hexagons" do +# expect(hex_ring.count).to eq count +# end +# end + +# context "when k range is 10" do +# let(:k) { 10 } +# let(:count) { 60 } + +# it "has 60 hexagons" do +# expect(hex_ring.count).to eq count +# end +# end + +# context "when the ring contains a pentagon" do +# let(:h3_index) { "821c07fffffffff".to_i(16) } +# let(:k) { 1 } + +# it "raises an error" do +# expect { hex_ring }.to raise_error(ArgumentError) +# end +# end +# end + +# describe ".distance" do +# let(:origin) { "89283082993ffff".to_i(16) } +# let(:destination) { "89283082827ffff".to_i(16) } +# let(:result) { 5 } + +# subject(:distance) { H3.distance(origin, destination) } + +# it { is_expected.to eq(result) } +# end + +# describe ".line_size" do +# let(:origin) { "89283082993ffff".to_i(16) } +# let(:destination) { "89283082827ffff".to_i(16) } +# let(:result) { 6 } + +# subject(:h3_line_size) { H3.line_size(origin, destination) } + +# it { is_expected.to eq(result) } +# end + +# describe ".line" do +# let(:origin) { "89283082993ffff".to_i(16) } +# let(:destination) { "89283082827ffff".to_i(16) } +# let(:result) do +# [ +# "89283082993ffff", "8928308299bffff", "892830829d7ffff", +# "892830829c3ffff", "892830829cbffff", "89283082827ffff" +# ].map { |i| i.to_i(16) } +# end + +# subject(:line) { H3.line(origin, destination) } + +# it { is_expected.to eq(result) } +# end end diff --git a/spec/unidirectional_edges_spec.rb b/spec/unidirectional_edges_spec.rb index 0c30856..09fc43f 100644 --- a/spec/unidirectional_edges_spec.rb +++ b/spec/unidirectional_edges_spec.rb @@ -1,119 +1,119 @@ RSpec.describe H3 do - include_context "constants" + # include_context "constants" - describe ".neighbors?" do - let(:origin) { "8928308280fffff".to_i(16) } - let(:destination) { "8928308280bffff".to_i(16) } - let(:result) { true } + # describe ".neighbors?" do + # let(:origin) { "8928308280fffff".to_i(16) } + # let(:destination) { "8928308280bffff".to_i(16) } + # let(:result) { true } - subject(:neighbors?) { H3.neighbors?(origin, destination) } + # subject(:neighbors?) { H3.neighbors?(origin, destination) } - it { is_expected.to eq(result) } + # it { is_expected.to eq(result) } - context "when the indexes aren't neighbors" do - let(:destination) { "89283082993ffff".to_i(16) } - let(:result) { false } + # context "when the indexes aren't neighbors" do + # let(:destination) { "89283082993ffff".to_i(16) } + # let(:result) { false } - it { is_expected.to eq(result) } - end - end + # it { is_expected.to eq(result) } + # end + # end - describe ".unidirectional_edge" do - let(:origin) { "8928308280fffff".to_i(16) } - let(:destination) { "8928308280bffff".to_i(16) } - let(:result) { "16928308280fffff".to_i(16) } + # describe ".unidirectional_edge" do + # let(:origin) { "8928308280fffff".to_i(16) } + # let(:destination) { "8928308280bffff".to_i(16) } + # let(:result) { "16928308280fffff".to_i(16) } - subject(:unidirectional_edge) { H3.unidirectional_edge(origin, destination) } + # subject(:unidirectional_edge) { H3.unidirectional_edge(origin, destination) } - it { is_expected.to eq(result) } - end + # it { is_expected.to eq(result) } + # end - describe ".unidirectional_edge_valid?" do - let(:edge) { "11928308280fffff".to_i(16) } - let(:result) { true } + # describe ".unidirectional_edge_valid?" do + # let(:edge) { "11928308280fffff".to_i(16) } + # let(:result) { true } - subject(:h3_unidirectional_edge_valid?) { H3.unidirectional_edge_valid?(edge) } + # subject(:h3_unidirectional_edge_valid?) { H3.unidirectional_edge_valid?(edge) } - it { is_expected.to eq(result) } + # it { is_expected.to eq(result) } - context "when the h3 index is not a valid unidirectional edge" do - let(:edge) { "8928308280fffff".to_i(16) } - let(:result) { false } + # context "when the h3 index is not a valid unidirectional edge" do + # let(:edge) { "8928308280fffff".to_i(16) } + # let(:result) { false } - it { is_expected.to eq(result) } - end - end + # it { is_expected.to eq(result) } + # end + # end - describe ".origin_from_unidirectional_edge" do - let(:edge) { "11928308280fffff".to_i(16) } - let(:result) { "8928308280fffff".to_i(16) } + # describe ".origin_from_unidirectional_edge" do + # let(:edge) { "11928308280fffff".to_i(16) } + # let(:result) { "8928308280fffff".to_i(16) } - subject(:origin_from_unidirectional_edge) { H3.origin_from_unidirectional_edge(edge) } + # subject(:origin_from_unidirectional_edge) { H3.origin_from_unidirectional_edge(edge) } - it { is_expected.to eq(result) } - end + # it { is_expected.to eq(result) } + # end - describe ".destination_from_unidirectional_edge" do - let(:edge) { "11928308280fffff".to_i(16) } - let(:result) { "8928308283bffff".to_i(16) } + # describe ".destination_from_unidirectional_edge" do + # let(:edge) { "11928308280fffff".to_i(16) } + # let(:result) { "8928308283bffff".to_i(16) } - subject(:destination_from_unidirectional_edge) { H3.destination_from_unidirectional_edge(edge) } + # subject(:destination_from_unidirectional_edge) { H3.destination_from_unidirectional_edge(edge) } - it { is_expected.to eq(result) } - end + # it { is_expected.to eq(result) } + # end - describe ".origin_and_destination_from_unidirectional_edge" do - let(:h3_index) { "11928308280fffff".to_i(16) } - let(:expected_indexes) do - %w(8928308280fffff 8928308283bffff).map { |i| i.to_i(16) } - end + # describe ".origin_and_destination_from_unidirectional_edge" do + # let(:h3_index) { "11928308280fffff".to_i(16) } + # let(:expected_indexes) do + # %w(8928308280fffff 8928308283bffff).map { |i| i.to_i(16) } + # end - subject(:origin_and_destination_from_unidirectional_edge) do - H3.origin_and_destination_from_unidirectional_edge(h3_index) - end + # subject(:origin_and_destination_from_unidirectional_edge) do + # H3.origin_and_destination_from_unidirectional_edge(h3_index) + # end - it "has two expected h3 indexes" do - expect(origin_and_destination_from_unidirectional_edge).to eq(expected_indexes) - end - end + # it "has two expected h3 indexes" do + # expect(origin_and_destination_from_unidirectional_edge).to eq(expected_indexes) + # end + # end - describe ".unidirectional_edges_from_hexagon" do - subject(:unidirectional_edges_from_hexagon) do - H3.unidirectional_edges_from_hexagon(h3_index) - end + # describe ".unidirectional_edges_from_hexagon" do + # subject(:unidirectional_edges_from_hexagon) do + # H3.unidirectional_edges_from_hexagon(h3_index) + # end - context "when index is a hexagon" do - let(:h3_index) { "8928308280fffff".to_i(16) } - let(:count) { 6 } + # context "when index is a hexagon" do + # let(:h3_index) { "8928308280fffff".to_i(16) } + # let(:count) { 6 } - it "has six expected edges" do - expect(unidirectional_edges_from_hexagon.count).to eq(count) - end - end + # it "has six expected edges" do + # expect(unidirectional_edges_from_hexagon.count).to eq(count) + # end + # end - context "when index is a pentagon" do - let(:h3_index) { "821c07fffffffff".to_i(16) } - let(:count) { 5 } + # context "when index is a pentagon" do + # let(:h3_index) { "821c07fffffffff".to_i(16) } + # let(:count) { 5 } - it "has five expected edges" do - expect(unidirectional_edges_from_hexagon.count).to eq(count) - end - end - end + # it "has five expected edges" do + # expect(unidirectional_edges_from_hexagon.count).to eq(count) + # end + # end + # end - describe ".unidirectional_edge_boundary" do - let(:edge) { "11928308280fffff".to_i(16) } - let(:expected) do - [[37.77820687262237, -122.41971895414808], [37.77652420699321, -122.42079024541876]] - end + # describe ".unidirectional_edge_boundary" do + # let(:edge) { "11928308280fffff".to_i(16) } + # let(:expected) do + # [[37.77820687262237, -122.41971895414808], [37.77652420699321, -122.42079024541876]] + # end - subject(:unidirectional_edge_boundary) { H3.unidirectional_edge_boundary(edge) } + # subject(:unidirectional_edge_boundary) { H3.unidirectional_edge_boundary(edge) } - it "matches expected coordinates" do - unidirectional_edge_boundary.zip(expected) do |(lat, lon), (exp_lat, exp_lon)| - expect(lat).to be_within(0.000001).of(exp_lat) - expect(lon).to be_within(0.000001).of(exp_lon) - end - end - end + # it "matches expected coordinates" do + # unidirectional_edge_boundary.zip(expected) do |(lat, lon), (exp_lat, exp_lon)| + # expect(lat).to be_within(0.000001).of(exp_lat) + # expect(lon).to be_within(0.000001).of(exp_lon) + # end + # end + # end end From 7f51448192075d8eae024d8337afd8af16917f77 Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Thu, 13 Oct 2022 08:46:02 +0100 Subject: [PATCH 02/10] These segfault - API signature changed. --- spec/miscellaneous_spec.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/miscellaneous_spec.rb b/spec/miscellaneous_spec.rb index f423cb7..cc929fb 100644 --- a/spec/miscellaneous_spec.rb +++ b/spec/miscellaneous_spec.rb @@ -37,23 +37,23 @@ it { is_expected.to eq(result) } end - describe ".hex_area_km2" do - let(:resolution) { 2 } - let(:result) { 86801.7803989972 } + # describe ".hex_area_km2" do + # let(:resolution) { 2 } + # let(:result) { 86801.7803989972 } - subject(:hex_area_km2) { H3.hex_area_km2(resolution) } + # subject(:hex_area_km2) { H3.hex_area_km2(resolution) } - it { is_expected.to eq(result) } - end + # it { is_expected.to eq(result) } + # end - describe ".hex_area_m2" do - let(:resolution) { 2 } - let(:result) { 86801780398.99731 } + # describe ".hex_area_m2" do + # let(:resolution) { 2 } + # let(:result) { 86801780398.99731 } - subject(:hex_area_m2) { H3.hex_area_m2(resolution) } + # subject(:hex_area_m2) { H3.hex_area_m2(resolution) } - it { is_expected.to eq(result) } - end + # it { is_expected.to eq(result) } + # end describe ".edge_length_km" do let(:resolution) { 2 } From c033eb30d84db58caa65d4c580126885804915d0 Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Fri, 14 Oct 2022 08:55:36 +0100 Subject: [PATCH 03/10] Update how we build the lib. --- Rakefile | 7 ++----- ext/h3/Makefile | 2 +- lib/h3/bindings/base.rb | 2 +- lib/h3/miscellaneous.rb | 8 ++++---- spec/hierarchy_spec.rb | 14 +++++++------- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Rakefile b/Rakefile index ae606c8..174a750 100644 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,7 @@ RSpec::Core::RakeTask.new(:spec) desc "Build H3 C library" task :build do - unless File.exist?("ext/h3/src/Makefile") + unless File.exist?("ext/h3/src/build/Makefile") `git submodule update --init --recursive` print "Building h3..." `cd ext/h3; make > /dev/null 2>&1` @@ -13,10 +13,7 @@ end desc "Remove compiled H3 library" task :clean do - File.delete("ext/h3/src/Makefile") if File.exist?("ext/h3/src/Makefile") - FileUtils.remove_dir("ext/h3/src/bin") if Dir.exist?("ext/h3/src/bin") - FileUtils.remove_dir("ext/h3/src/generated") if Dir.exist?("ext/h3/src/generated") - FileUtils.remove_dir("ext/h3/src/lib") if Dir.exist?("ext/h3/src/lib") + FileUtils.remove_dir("ext/h3/src/build") if Dir.exist?("ext/h3/src/build") end task spec: :build diff --git a/ext/h3/Makefile b/ext/h3/Makefile index b69b9ec..4eec44e 100644 --- a/ext/h3/Makefile +++ b/ext/h3/Makefile @@ -3,4 +3,4 @@ make: install: : # do nothing, we'll load the lib directly clean: - : # do nothing, cleanup happens when gem uninstalled + rm -rf build \ No newline at end of file diff --git a/lib/h3/bindings/base.rb b/lib/h3/bindings/base.rb index 05220bd..26e98f4 100644 --- a/lib/h3/bindings/base.rb +++ b/lib/h3/bindings/base.rb @@ -5,7 +5,7 @@ module Bindings # When extended, this module sets up FFI to use the H3 C library. module Base def self.extended(base) - lib_path = File.expand_path(__dir__ + "/../../../ext/h3/src/lib") + lib_path = File.expand_path(__dir__ + "/../../../ext/h3/src/build/lib") base.extend FFI::Library base.extend Gem::Deprecate base.include Structs diff --git a/lib/h3/miscellaneous.rb b/lib/h3/miscellaneous.rb index 7cd149f..0387f87 100644 --- a/lib/h3/miscellaneous.rb +++ b/lib/h3/miscellaneous.rb @@ -44,7 +44,7 @@ def edge_length_km(resolution) # 3229.482772 # # @return [Float] Length of edge in metres - attach_function :edge_length_m, :distanceM, [Resolution], :double + # attach_function :edge_length_m, :distanceM, [Resolution], :double # @!method hex_area_km2(resolution) # @@ -169,7 +169,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in rads - attach_function :exact_edge_length_rads, :exactEdgeLengthRads, %i[h3_index], :double + # attach_function :exact_edge_length_rads, :exactEdgeLengthRads, %i[h3_index], :double # @!method exact_edge_length_km # @@ -180,7 +180,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in kilometres - attach_function :exact_edge_length_km, :exactEdgeLengthKm, %i[h3_index], :double + # attach_function :exact_edge_length_km, :exactEdgeLengthKm, %i[h3_index], :double # @!method exact_edge_length_m # @@ -191,7 +191,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in metres - attach_function :exact_edge_length_m, :exactEdgeLengthM, %i[h3_index], :double + # attach_function :exact_edge_length_m, :exactEdgeLengthM, %i[h3_index], :double # Returns the radians distance between two points. # diff --git a/spec/hierarchy_spec.rb b/spec/hierarchy_spec.rb index ab2b854..ebd5a34 100644 --- a/spec/hierarchy_spec.rb +++ b/spec/hierarchy_spec.rb @@ -189,13 +189,13 @@ end end - describe ".center_child" do - let(:h3_index) { "8828308299fffff".to_i(16) } - let(:resolution) { 10 } - let(:result) { "8a2830829807fff".to_i(16) } + # describe ".center_child" do + # let(:h3_index) { "8828308299fffff".to_i(16) } + # let(:resolution) { 10 } + # let(:result) { "8a2830829807fff".to_i(16) } - subject(:center_child) { H3.center_child(h3_index, resolution) } + # subject(:center_child) { H3.center_child(h3_index, resolution) } - it { is_expected.to eq result } - end + # it { is_expected.to eq result } + # end end From 46a76062984f6e4ac3a1161d67eee42df127f3f8 Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Sat, 15 Oct 2022 05:01:10 +0100 Subject: [PATCH 04/10] Set the right version of H3. --- ext/h3/src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/h3/src b/ext/h3/src index 26a6409..5035cd1 160000 --- a/ext/h3/src +++ b/ext/h3/src @@ -1 +1 @@ -Subproject commit 26a6409156ba8539b2b332f799486572f1f8bab2 +Subproject commit 5035cd1158f2c78fcfbd71d25a08c6218a09ad52 From b04810e00514c8fb8dfd1c92426d44de0af45e16 Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Sat, 15 Oct 2022 12:41:24 +0100 Subject: [PATCH 05/10] Test for dir. --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 174a750..bbdf339 100644 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,7 @@ RSpec::Core::RakeTask.new(:spec) desc "Build H3 C library" task :build do - unless File.exist?("ext/h3/src/build/Makefile") + unless Dir.exist?("ext/h3/src/build") `git submodule update --init --recursive` print "Building h3..." `cd ext/h3; make > /dev/null 2>&1` From a228993fc01e7c20efaeffb00b3e16558bd609af Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Sat, 22 Mar 2025 12:52:32 +0000 Subject: [PATCH 06/10] Update bindings for H3 v4.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update function names to match H3 v4 API - Implement error handling for all functions to use new error code system - Fix edge functions to use directed edge nomenclature - Fix memory handling for C parameters - Re-enable commented functions with proper v4 equivalents 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/h3/bindings/private.rb | 87 +++++++++++++++----------------- lib/h3/inspection.rb | 4 +- lib/h3/miscellaneous.rb | 18 ++++--- lib/h3/traversal.rb | 90 +++++++++++++++++++++++++++------- lib/h3/unidirectional_edges.rb | 12 +++-- 5 files changed, 135 insertions(+), 76 deletions(-) diff --git a/lib/h3/bindings/private.rb b/lib/h3/bindings/private.rb index e0f463c..64d111d 100644 --- a/lib/h3/bindings/private.rb +++ b/lib/h3/bindings/private.rb @@ -15,63 +15,54 @@ def self.safe_call(out_type, method, *in_args) out.send("read_#{out_type}".to_sym) end + # Hierarchy functions attach_function :cellToParent, [:h3_index, Resolution, H3Index], :h3_error_code attach_function :compactCells, [H3IndexesIn, H3IndexesOut, :int64], :h3_error_code - # attach_function :destroy_linked_polygon, :destroyLinkedPolygon, [LinkedGeoPolygon], :void - attach_function :edge_length_km, :getHexagonEdgeLengthAvgKm, [Resolution, :pointer], :h3_error_code + attach_function :h3_to_children, :cellToChildren, [:h3_index, Resolution, H3IndexesOut], :h3_error_code + attach_function :max_children, :cellToChildrenSize, [:h3_index, Resolution, :pointer], :h3_error_code + attach_function :max_uncompact_size, :uncompactCellsSize, [H3IndexesIn, :int64, Resolution, :pointer], :h3_error_code + attach_function :uncompactCells, [H3IndexesIn, :size_t, H3IndexesOut, :size_t, Resolution], :h3_error_code + + # Indexing functions attach_function :from_string, :stringToH3, %i[string pointer], :h3_error_code attach_function :geo_to_h3, :latLngToCell, [LatLng, Resolution, :pointer], :h3_error_code - # attach_function :get_pentagon_indexes, :getPentagonIndexes, [:int, H3IndexesOut], :void - attach_function :hexagon_count, :getNumCells, [Resolution, :pointer], :h3_error_code - attach_function :h3_faces, :getIcosahedronFaces, %i[h3_index buffer_out], :h3_error_code - # attach_function :h3_indexes_from_unidirectional_edge, - # :getH3IndexesFromUnidirectionalEdge, - # [:h3_index, H3IndexesOut], :void - # attach_function :h3_line, :h3Line, [:h3_index, :h3_index, H3IndexesOut], :int - # attach_function :h3_unidirectional_edges_from_hexagon, - # :getH3UnidirectionalEdgesFromHexagon, - # [:h3_index, H3IndexesOut], :void - # attach_function :h3_set_to_linked_geo, - # :h3SetToLinkedGeo, - # [H3IndexesIn, :size_t, LinkedGeoPolygon], - # :void - attach_function :h3_to_children, :cellToChildren, [:h3_index, Resolution, H3IndexesOut], :h3_error_code attach_function :h3_to_geo, :cellToLatLng, [:h3_index, LatLng], :h3_error_code attach_function :h3_to_string, :h3ToString, %i[h3_index buffer_out size_t], :h3_error_code - attach_function :h3_to_geo_boundary, - :cellToBoundary, - [:h3_index, CellBoundary], - :h3_error_code - # attach_function :h3_unidirectional_edge_boundary, - # :getH3UnidirectionalEdgeBoundary, - # [:h3_index, CellBoundary], :void - # attach_function :hex_range, :hexRange, [:h3_index, :k_distance, H3IndexesOut], :bool - # attach_function :hex_range_distances, - # :hexRangeDistances, - # [:h3_index, :k_distance, H3IndexesOut, :buffer_out], :bool - # attach_function :hex_ranges, - # :hexRanges, - # [H3IndexesIn, :size_t, :k_distance, H3IndexesOut], - # :bool - # attach_function :hex_ring, :hexRing, [:h3_index, :k_distance, H3IndexesOut], :bool + attach_function :h3_to_geo_boundary, :cellToBoundary, [:h3_index, CellBoundary], :h3_error_code + + # Traversal functions attach_function :k_ring, :gridDisk, [:h3_index, :k_distance, H3IndexesOut], :h3_error_code - # attach_function :k_ring_distances, - # :kRingDistances, - # [:h3_index, :k_distance, H3IndexesOut, :buffer_out], - # :bool - attach_function :max_children, :cellToChildrenSize, [:h3_index, Resolution, :pointer], :h3_error_code + attach_function :k_ring_distances, :gridDiskDistances, [:h3_index, :k_distance, H3IndexesOut, :pointer], :h3_error_code + attach_function :hex_range, :gridDiskUnsafe, [:h3_index, :k_distance, H3IndexesOut], :h3_error_code + attach_function :hex_range_distances, :gridDiskDistancesUnsafe, [:h3_index, :k_distance, H3IndexesOut, :pointer], :h3_error_code + attach_function :hex_ranges, :gridDisksUnsafe, [H3IndexesIn, :size_t, :k_distance, H3IndexesOut], :h3_error_code + attach_function :hex_ring, :gridRingUnsafe, [:h3_index, :k_distance, H3IndexesOut], :h3_error_code + attach_function :h3_line, :gridPathCells, [:h3_index, :h3_index, H3IndexesOut], :h3_error_code + + # Directed edge functions (formerly unidirectional_edge) + attach_function :h3_indexes_from_unidirectional_edge, :directedEdgeToCells, [:h3_index, H3IndexesOut], :h3_error_code + attach_function :h3_unidirectional_edges_from_hexagon, :originToDirectedEdges, [:h3_index, H3IndexesOut], :h3_error_code + attach_function :h3_unidirectional_edge_boundary, :directedEdgeToBoundary, [:h3_index, CellBoundary], :h3_error_code + + # Miscellaneous functions + attach_function :hexagon_count, :getNumCells, [Resolution, :pointer], :h3_error_code + attach_function :edge_length_km, :getHexagonEdgeLengthAvgKm, [Resolution, :pointer], :h3_error_code + attach_function :edge_length_m, :getHexagonEdgeLengthAvgM, [Resolution, :pointer], :h3_error_code + attach_function :h3_faces, :getIcosahedronFaces, %i[h3_index buffer_out], :h3_error_code attach_function :max_face_count, :maxFaceCount, %i[h3_index pointer], :h3_error_code - # attach_function :max_polyfill_size, - # :maxPolyfillSize, - # [GeoPolygon, Resolution], - # :int - attach_function :max_uncompact_size, :uncompactCellsSize, [H3IndexesIn, :int64, Resolution, :pointer], :h3_error_code - # attach_function :point_distance_rads, :pointDistRads, [GeoCoord, GeoCoord], :double - # attach_function :point_distance_km, :pointDistKm, [GeoCoord, GeoCoord], :double - # attach_function :point_distance_m, :pointDistM, [GeoCoord, GeoCoord], :double - # attach_function :polyfill, [GeoPolygon, Resolution, H3IndexesOut], :void + attach_function :get_pentagon_indexes, :getPentagonCells, [:int, H3IndexesOut], :h3_error_code attach_function :res_0_indexes, :getRes0Cells, [H3IndexesOut], :h3_error_code - attach_function :uncompactCells, [H3IndexesIn, :size_t, H3IndexesOut, :size_t, Resolution], :h3_error_code + + # Distance functions + attach_function :point_distance_rads, :greatCircleDistanceRads, [LatLng, LatLng], :double + attach_function :point_distance_km, :greatCircleDistanceKm, [LatLng, LatLng], :double + attach_function :point_distance_m, :greatCircleDistanceM, [LatLng, LatLng], :double + + # Region functions + attach_function :destroy_linked_polygon, :destroyLinkedMultiPolygon, [LinkedGeoPolygon], :void + attach_function :h3_set_to_linked_geo, :cellsToLinkedMultiPolygon, [H3IndexesIn, :size_t, LinkedGeoPolygon], :h3_error_code + attach_function :max_polyfill_size, :maxPolygonToCellsSize, [GeoPolygon, Resolution, :pointer], :h3_error_code + attach_function :polyfill, :polygonToCells, [GeoPolygon, Resolution, H3IndexesOut], :h3_error_code end end end \ No newline at end of file diff --git a/lib/h3/inspection.rb b/lib/h3/inspection.rb index 1ea6164..c5ed1bc 100644 --- a/lib/h3/inspection.rb +++ b/lib/h3/inspection.rb @@ -103,7 +103,9 @@ def from_string(str) # @return [String] H3 index in hexadecimal form. def to_string(h3_index) h3_str = FFI::MemoryPointer.new(:char, H3_TO_STR_BUF_SIZE) - Bindings::Private.h3_to_string(h3_index, h3_str, H3_TO_STR_BUF_SIZE) + Bindings::Private.h3_to_string(h3_index, h3_str, H3_TO_STR_BUF_SIZE).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end h3_str.read_string end diff --git a/lib/h3/miscellaneous.rb b/lib/h3/miscellaneous.rb index 0387f87..18c163d 100644 --- a/lib/h3/miscellaneous.rb +++ b/lib/h3/miscellaneous.rb @@ -44,7 +44,9 @@ def edge_length_km(resolution) # 3229.482772 # # @return [Float] Length of edge in metres - # attach_function :edge_length_m, :distanceM, [Resolution], :double + def edge_length_m(resolution) + Bindings::Private.safe_call(:double, :edge_length_m, resolution) + end # @!method hex_area_km2(resolution) # @@ -169,7 +171,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in rads - # attach_function :exact_edge_length_rads, :exactEdgeLengthRads, %i[h3_index], :double + attach_function :exact_edge_length_rads, :getDirectedEdgeLengthRads, %i[h3_index], :double # @!method exact_edge_length_km # @@ -180,7 +182,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in kilometres - # attach_function :exact_edge_length_km, :exactEdgeLengthKm, %i[h3_index], :double + attach_function :exact_edge_length_km, :getDirectedEdgeLengthKm, %i[h3_index], :double # @!method exact_edge_length_m # @@ -191,7 +193,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in metres - # attach_function :exact_edge_length_m, :exactEdgeLengthM, %i[h3_index], :double + attach_function :exact_edge_length_m, :getDirectedEdgeLengthM, %i[h3_index], :double # Returns the radians distance between two points. # @@ -235,7 +237,9 @@ def point_distance_m(origin, destination) # @return [Array] All resolution 0 hexagons (base cells). def base_cells out = H3Indexes.of_size(base_cell_count) - Bindings::Private.res_0_indexes(out) + Bindings::Private.res_0_indexes(out).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end out.read end @@ -248,7 +252,9 @@ def base_cells # @return [Array] All pentagon indexes at the given resolution. def pentagons(resolution) out = H3Indexes.of_size(pentagon_count) - Bindings::Private.get_pentagon_indexes(resolution, out) + Bindings::Private.get_pentagon_indexes(resolution, out).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end out.read end diff --git a/lib/h3/traversal.rb b/lib/h3/traversal.rb index 405a075..44e5055 100644 --- a/lib/h3/traversal.rb +++ b/lib/h3/traversal.rb @@ -10,13 +10,24 @@ module Traversal # Derive the maximum k-ring size for distance k. # # @param [Integer] k K value. + # @param [FFI::MemoryPointer] out Optional memory pointer for storing result (for internal use). # # @example Derive the maximum k-ring size for k=5 # H3.max_kring_size(5) # 91 # - # @return [Integer] Maximum k-ring size. - attach_function :max_kring_size, :maxGridDiskSize, %i[k_distance pointer], :h3_error_code + # @return [Integer] Maximum k-ring size if out is nil, otherwise returns error code. + def max_kring_size(k, out = nil) + if out + return Bindings::Private.max_kring_size(k, out) + else + out_ptr = FFI::MemoryPointer.new(:int64) + Bindings::Private.max_kring_size(k, out_ptr).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end + out_ptr.read_int64 + end + end # @!method distance(origin, h3_index) # @@ -80,10 +91,17 @@ module Traversal # # @return [Array] Array of H3 indexes within the k-range. def hex_range(origin, k) - max_hexagons = max_kring_size(k) - out = H3Indexes.of_size(max_hexagons) - pentagonal_distortion = Bindings::Private.hex_range(origin, k, out) - raise(ArgumentError, "Specified hexagon range contains a pentagon") if pentagonal_distortion + max_size = FFI::MemoryPointer.new(:int64) + max_kring_size(k, max_size).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end + + out = H3Indexes.of_size(max_size.read_int64) + Bindings::Private.hex_range(origin, k, out).tap do |code| + # The 'pentagon distortion' error in H3 v4 is error code 9 + raise(ArgumentError, "Specified hexagon range contains a pentagon") if code == 9 + Bindings::Error::raise_error(code) unless code.zero? + end out.read end @@ -141,8 +159,13 @@ def k_ring(origin, k) def hex_ring(origin, k) max_hexagons = max_hex_ring_size(k) out = H3Indexes.of_size(max_hexagons) - pentagonal_distortion = Bindings::Private.hex_ring(origin, k, out) - raise(ArgumentError, "The hex ring contains a pentagon") if pentagonal_distortion + + Bindings::Private.hex_ring(origin, k, out).tap do |code| + # The 'pentagon distortion' error in H3 v4 is error code 9 + raise(ArgumentError, "The hex ring contains a pentagon") if code == 9 + Bindings::Error::raise_error(code) unless code.zero? + end + out.read end @@ -236,11 +259,20 @@ def hex_ranges(h3_set, k, grouped: true) # # @return [Hash] Hex range grouped by distance. def hex_range_distances(origin, k) - max_out_size = max_kring_size(k) + max_size = FFI::MemoryPointer.new(:int64) + max_kring_size(k, max_size).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end + + max_out_size = max_size.read_int64 out = H3Indexes.of_size(max_out_size) distances = FFI::MemoryPointer.new(:int, max_out_size) - pentagonal_distortion = Bindings::Private.hex_range_distances(origin, k, out, distances) - raise(ArgumentError, "Specified hexagon range contains a pentagon") if pentagonal_distortion + + Bindings::Private.hex_range_distances(origin, k, out, distances).tap do |code| + # The 'pentagon distortion' error in H3 v4 is error code 9 + raise(ArgumentError, "Specified hexagon range contains a pentagon") if code == 9 + Bindings::Error::raise_error(code) unless code.zero? + end hexagons = out.read distances = distances.read_array_of_int(max_out_size) @@ -273,10 +305,18 @@ def hex_range_distances(origin, k) # # @return [Hash] Hash of k-ring distances grouped by distance. def k_ring_distances(origin, k) - max_out_size = max_kring_size(k) + max_size = FFI::MemoryPointer.new(:int64) + max_kring_size(k, max_size).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end + + max_out_size = max_size.read_int64 out = H3Indexes.of_size(max_out_size) distances = FFI::MemoryPointer.new(:int, max_out_size) - Bindings::Private.k_ring_distances(origin, k, out, distances) + + Bindings::Private.k_ring_distances(origin, k, out, distances).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end hexagons = out.read distances = distances.read_array_of_int(max_out_size) @@ -305,8 +345,12 @@ def k_ring_distances(origin, k) def line(origin, destination) max_hexagons = line_size(origin, destination) hexagons = H3Indexes.of_size(max_hexagons) - res = Bindings::Private.h3_line(origin, destination, hexagons) - raise(ArgumentError, "Could not compute line") if res.negative? + + Bindings::Private.h3_line(origin, destination, hexagons).tap do |code| + # In H3 v4, this could be various error codes + Bindings::Error::raise_error(code) unless code.zero? + end + hexagons.read end @@ -322,10 +366,20 @@ def k_rings_for_hex_range(indexes, k) def hex_ranges_ungrouped(h3_set, k) h3_set = H3Indexes.with_contents(h3_set) - max_out_size = h3_set.size * max_kring_size(k) + + # Calculate max output size + max_out_per_cell = FFI::MemoryPointer.new(:int64) + Bindings::Private.max_kring_size(k, max_out_per_cell).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end + max_out_size = h3_set.size * max_out_per_cell.read_int64 + out = H3Indexes.of_size(max_out_size) - if Bindings::Private.hex_ranges(h3_set, h3_set.size, k, out) - raise(ArgumentError, "One of the specified hexagon ranges contains a pentagon") + + Bindings::Private.hex_ranges(h3_set, h3_set.size, k, out).tap do |code| + # The 'pentagon distortion' error in H3 v4 is error code 9 + raise(ArgumentError, "One of the specified hexagon ranges contains a pentagon") if code == 9 + Bindings::Error::raise_error(code) unless code.zero? end out.read diff --git a/lib/h3/unidirectional_edges.rb b/lib/h3/unidirectional_edges.rb index fef97ef..497377b 100644 --- a/lib/h3/unidirectional_edges.rb +++ b/lib/h3/unidirectional_edges.rb @@ -100,7 +100,9 @@ module UnidirectionalEdges def origin_and_destination_from_unidirectional_edge(edge) max_hexagons = 2 out = H3Indexes.of_size(max_hexagons) - Bindings::Private.h3_indexes_from_unidirectional_edge(edge, out) + Bindings::Private.h3_indexes_from_unidirectional_edge(edge, out).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end out.read end @@ -119,7 +121,9 @@ def origin_and_destination_from_unidirectional_edge(edge) def unidirectional_edges_from_hexagon(origin) max_edges = 6 out = H3Indexes.of_size(max_edges) - Bindings::Private.h3_unidirectional_edges_from_hexagon(origin, out) + Bindings::Private.h3_unidirectional_edges_from_hexagon(origin, out).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end out.read end @@ -138,7 +142,9 @@ def unidirectional_edges_from_hexagon(origin) # @return [Array>] Edge boundary coordinates for a hexagon def unidirectional_edge_boundary(edge) geo_boundary = CellBoundary.new - Bindings::Private.h3_unidirectional_edge_boundary(edge, geo_boundary) + Bindings::Private.h3_unidirectional_edge_boundary(edge, geo_boundary).tap do |code| + Bindings::Error::raise_error(code) unless code.zero? + end geo_boundary[:verts].take(geo_boundary[:num_verts]).map do |d| [rads_to_degs(d[:lat]), rads_to_degs(d[:lon])] end From 083d96d61c30ebf9fe758484e4d91798cfef4301 Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Sat, 22 Mar 2025 13:10:17 +0000 Subject: [PATCH 07/10] Fix H3 v4.0.0 API function bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated edge length function names: getDirectedEdgeLength* -> edgeLength* - Updated pentagon function name: getPentagonCells -> getPentagons - Added maxGridDiskSize binding for traversal functions - Updated edge_length_km test expectation to match H3 v4.0.0 values 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/h3/bindings/private.rb | 3 ++- lib/h3/miscellaneous.rb | 6 +++--- spec/miscellaneous_spec.rb | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/h3/bindings/private.rb b/lib/h3/bindings/private.rb index 64d111d..56cb380 100644 --- a/lib/h3/bindings/private.rb +++ b/lib/h3/bindings/private.rb @@ -38,6 +38,7 @@ def self.safe_call(out_type, method, *in_args) attach_function :hex_ranges, :gridDisksUnsafe, [H3IndexesIn, :size_t, :k_distance, H3IndexesOut], :h3_error_code attach_function :hex_ring, :gridRingUnsafe, [:h3_index, :k_distance, H3IndexesOut], :h3_error_code attach_function :h3_line, :gridPathCells, [:h3_index, :h3_index, H3IndexesOut], :h3_error_code + attach_function :max_kring_size, :maxGridDiskSize, [:k_distance, :pointer], :h3_error_code # Directed edge functions (formerly unidirectional_edge) attach_function :h3_indexes_from_unidirectional_edge, :directedEdgeToCells, [:h3_index, H3IndexesOut], :h3_error_code @@ -50,7 +51,7 @@ def self.safe_call(out_type, method, *in_args) attach_function :edge_length_m, :getHexagonEdgeLengthAvgM, [Resolution, :pointer], :h3_error_code attach_function :h3_faces, :getIcosahedronFaces, %i[h3_index buffer_out], :h3_error_code attach_function :max_face_count, :maxFaceCount, %i[h3_index pointer], :h3_error_code - attach_function :get_pentagon_indexes, :getPentagonCells, [:int, H3IndexesOut], :h3_error_code + attach_function :get_pentagon_indexes, :getPentagons, [:int, H3IndexesOut], :h3_error_code attach_function :res_0_indexes, :getRes0Cells, [H3IndexesOut], :h3_error_code # Distance functions diff --git a/lib/h3/miscellaneous.rb b/lib/h3/miscellaneous.rb index 18c163d..7afbe89 100644 --- a/lib/h3/miscellaneous.rb +++ b/lib/h3/miscellaneous.rb @@ -171,7 +171,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in rads - attach_function :exact_edge_length_rads, :getDirectedEdgeLengthRads, %i[h3_index], :double + attach_function :exact_edge_length_rads, :edgeLengthRads, %i[h3_index], :double # @!method exact_edge_length_km # @@ -182,7 +182,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in kilometres - attach_function :exact_edge_length_km, :getDirectedEdgeLengthKm, %i[h3_index], :double + attach_function :exact_edge_length_km, :edgeLengthKm, %i[h3_index], :double # @!method exact_edge_length_m # @@ -193,7 +193,7 @@ def hexagon_count(resolution) # 3.287684056071637e-05 # # @return [Double] Edge length in metres - attach_function :exact_edge_length_m, :getDirectedEdgeLengthM, %i[h3_index], :double + attach_function :exact_edge_length_m, :edgeLengthM, %i[h3_index], :double # Returns the radians distance between two points. # diff --git a/spec/miscellaneous_spec.rb b/spec/miscellaneous_spec.rb index cc929fb..b6237ac 100644 --- a/spec/miscellaneous_spec.rb +++ b/spec/miscellaneous_spec.rb @@ -57,7 +57,7 @@ describe ".edge_length_km" do let(:resolution) { 2 } - let(:result) { 158.2446558 } + let(:result) { 182.5129565 } subject(:edge_length_km) { H3.edge_length_km(resolution) } From 2956bb641880d53e238c8d1d53591ac09797e13b Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Sat, 22 Mar 2025 13:12:07 +0000 Subject: [PATCH 08/10] We won't build H3 any more. --- Rakefile | 20 -------------------- ext/h3/extconf.rb | 2 -- ext/h3/src | 1 - h3.gemspec | 1 - lib/h3/bindings/base.rb | 3 +-- spec/spec_helper.rb | 6 +----- 6 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 ext/h3/extconf.rb delete mode 160000 ext/h3/src diff --git a/Rakefile b/Rakefile index bbdf339..8d4a1d2 100644 --- a/Rakefile +++ b/Rakefile @@ -1,24 +1,4 @@ require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) -desc "Build H3 C library" -task :build do - unless Dir.exist?("ext/h3/src/build") - `git submodule update --init --recursive` - print "Building h3..." - `cd ext/h3; make > /dev/null 2>&1` - puts " done." - end -end - -desc "Remove compiled H3 library" -task :clean do - FileUtils.remove_dir("ext/h3/src/build") if Dir.exist?("ext/h3/src/build") -end - -task spec: :build - -desc "Recompile the H3 C library" -task rebuild: %i[clean build] - task default: :spec diff --git a/ext/h3/extconf.rb b/ext/h3/extconf.rb deleted file mode 100644 index 59b9de7..0000000 --- a/ext/h3/extconf.rb +++ /dev/null @@ -1,2 +0,0 @@ -# Do nothing, Makefile is already made. -# This file keeps `gem install` happy. diff --git a/ext/h3/src b/ext/h3/src deleted file mode 160000 index 5035cd1..0000000 --- a/ext/h3/src +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5035cd1158f2c78fcfbd71d25a08c6218a09ad52 diff --git a/h3.gemspec b/h3.gemspec index 64e377c..2b4ed13 100644 --- a/h3.gemspec +++ b/h3.gemspec @@ -23,5 +23,4 @@ Gem::Specification.new do |spec| spec.add_development_dependency "yard", "~> 0.9" spec.add_development_dependency "pry", "~> 0.14" - spec.extensions << "ext/h3/extconf.rb" end diff --git a/lib/h3/bindings/base.rb b/lib/h3/bindings/base.rb index 26e98f4..95bec6e 100644 --- a/lib/h3/bindings/base.rb +++ b/lib/h3/bindings/base.rb @@ -5,12 +5,11 @@ module Bindings # When extended, this module sets up FFI to use the H3 C library. module Base def self.extended(base) - lib_path = File.expand_path(__dir__ + "/../../../ext/h3/src/build/lib") base.extend FFI::Library base.extend Gem::Deprecate base.include Structs base.include Types - base.ffi_lib ["#{lib_path}/libh3.dylib", "#{lib_path}/libh3.so"] + base.ffi_lib ["libh3.dylib", "libh3.so"] base.typedef :ulong_long, :h3_index base.typedef :int, :k_distance base.typedef :uint, :h3_error_code diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 216b035..7d92e05 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,11 +3,7 @@ # require "coveralls" # Coveralls.wear! -begin - require "h3" -rescue LoadError - raise "Could not load h3. Run `rake rebuild` to recompile." -end +require "h3" # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. From 8c950b76ea8279f197e1768f0d1c8c530d69097e Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Sat, 22 Mar 2025 13:27:31 +0000 Subject: [PATCH 09/10] May as well update Ruby. --- .ruby-version | 2 +- Gemfile.lock | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.ruby-version b/.ruby-version index 37c2961..4d9d11c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.2 +3.4.2 diff --git a/Gemfile.lock b/Gemfile.lock index ad930a5..6199875 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,11 +9,12 @@ PATH GEM remote: https://rubygems.org/ specs: + coderay (1.1.3) diff-lcs (1.5.1) ffi (1.17.1) - multi_json (1.15.0) method_source (1.0.0) - pry (0.14.1) + multi_json (1.15.0) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) rake (13.2.1) @@ -38,7 +39,7 @@ GEM zeitwerk (2.6.18) PLATFORMS - ruby + arm64-darwin-24 DEPENDENCIES h3! @@ -48,4 +49,4 @@ DEPENDENCIES yard (~> 0.9) BUNDLED WITH - 2.1.4 \ No newline at end of file + 2.6.6 From 6825c5d5f07a6c34bf9e0576394ab46202ce1059 Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Fri, 4 Apr 2025 13:33:17 +0100 Subject: [PATCH 10/10] Install H3 on CI. --- .github/workflows/ruby_ci.yml | 11 ++++++++-- Gemfile.lock | 40 ++++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ruby_ci.yml b/.github/workflows/ruby_ci.yml index 0b56995..22ae1c2 100644 --- a/.github/workflows/ruby_ci.yml +++ b/.github/workflows/ruby_ci.yml @@ -11,9 +11,15 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] + # os: [ubuntu-latest, macos-latest] + os: [macos-latest] # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0' - ruby: ['2.7', '3.0', '3.1', '3.2', '3.3'] + ruby: ['3.2', '3.3', '3.4'] + include: + # - os: ubuntu-latest + # install_h3: git clone https://github.com/uber/h3.git && cd h3 && git checkout v4.2.1 && sudo apt install cmake make gcc libtool && mkdir build && cd build && cmake -DBUILD_SHARED_LIBS=true .. && cmake --build . + - os: macos-latest + install_h3: brew install h3 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -21,4 +27,5 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - run: ${{ matrix.install_h3 }} - run: bundle exec rake diff --git a/Gemfile.lock b/Gemfile.lock index 6199875..2d47f29 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,15 +10,25 @@ GEM remote: https://rubygems.org/ specs: coderay (1.1.3) - diff-lcs (1.5.1) + diff-lcs (1.6.1) ffi (1.17.1) - method_source (1.0.0) + ffi (1.17.1-aarch64-linux-gnu) + ffi (1.17.1-aarch64-linux-musl) + ffi (1.17.1-arm-linux-gnu) + ffi (1.17.1-arm-linux-musl) + ffi (1.17.1-arm64-darwin) + ffi (1.17.1-x86-linux-gnu) + ffi (1.17.1-x86-linux-musl) + ffi (1.17.1-x86_64-darwin) + ffi (1.17.1-x86_64-linux-gnu) + ffi (1.17.1-x86_64-linux-musl) + method_source (1.1.0) multi_json (1.15.0) pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) rake (13.2.1) - rgeo (3.0.0) + rgeo (3.0.1) rgeo-geojson (2.2.0) multi_json (~> 1.15) rgeo (>= 1.0.0) @@ -26,20 +36,30 @@ GEM rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.3) rspec-support (~> 3.13.0) - rspec-expectations (3.13.0) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.0) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-support (3.13.1) + rspec-support (3.13.2) yard (0.9.37) - zeitwerk (2.6.18) + zeitwerk (2.7.2) PLATFORMS - arm64-darwin-24 + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + ruby + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES h3! @@ -49,4 +69,4 @@ DEPENDENCIES yard (~> 0.9) BUNDLED WITH - 2.6.6 + 2.6.7