From 2201262572d9a049d3655a00bdd5e9ec129d7a2e Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Tue, 13 Oct 2020 07:39:27 +0100 Subject: [PATCH] Update H3 to 3.7.1 & add new functions (#70) --- CHANGELOG.md | 19 ++++++ Gemfile.lock | 6 +- ext/h3/src | 2 +- lib/h3/bindings/private.rb | 3 + lib/h3/miscellaneous.rb | 123 +++++++++++++++++++++++++++++++++++++ lib/h3/version.rb | 2 +- spec/miscellaneous_spec.rb | 117 +++++++++++++++++++++++++++++++++++ 7 files changed, 267 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e952459..8312026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ This project adheres to [Semantic Versioning](http://semver.org/). We track the MAJOR and MINOR version levels of Uber's H3 project (https://github.com/uber/h3) but maintain independent patch levels so we can make small fixes and non breaking changes. +## [3.7.1] - 2020-10-7 +### Added +- Area and haversine distance functions: + - `cellAreaRads2` + - `cellAreaKm2` + - `cellAreaM2` + - `pointDistRads` + - `pointDistKm` + - `pointDistM` + - `exactEdgeLengthRads` + - `exactEdgeLengthKm` + - `exactEdgeLengthM` + +### Changed +- Speeds up `getH3UnidirectionalEdgeBoundary` by about 3x. + +### Fixed +- Finding invalid edge boundaries should not crash. + ## [3.6.4] - 2020-7-2 ### Changed - Reinstate new `polyfill` algorithm for up to 3x perf boost. diff --git a/Gemfile.lock b/Gemfile.lock index 750201a..c624047 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - h3 (3.6.4) + h3 (3.7.1) ffi (~> 1.9) rgeo-geojson (~> 2.1) zeitwerk (~> 2.1) @@ -46,7 +46,7 @@ GEM thor (0.19.4) tins (1.20.2) yard (0.9.20) - zeitwerk (2.3.1) + zeitwerk (2.4.0) PLATFORMS ruby @@ -59,4 +59,4 @@ DEPENDENCIES yard (~> 0.9) BUNDLED WITH - 1.17.3 + 2.1.4 diff --git a/ext/h3/src b/ext/h3/src index 93b5a30..ee292a9 160000 --- a/ext/h3/src +++ b/ext/h3/src @@ -1 +1 @@ -Subproject commit 93b5a30adef3c60ff4a7dd773f03dba040eb8673 +Subproject commit ee292a96906767162f2f530b46fc2428b2555748 diff --git a/lib/h3/bindings/private.rb b/lib/h3/bindings/private.rb index 1c038e9..23cc441 100644 --- a/lib/h3/bindings/private.rb +++ b/lib/h3/bindings/private.rb @@ -52,6 +52,9 @@ module Private [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 :uncompact, [H3IndexesIn, :size_t, H3IndexesOut, :size_t, Resolution], :bool diff --git a/lib/h3/miscellaneous.rb b/lib/h3/miscellaneous.rb index dd47240..47ec8bd 100644 --- a/lib/h3/miscellaneous.rb +++ b/lib/h3/miscellaneous.rb @@ -119,6 +119,105 @@ module Miscellaneous # @return [Integer] The number of pentagons per resolution. attach_function :pentagon_count, :pentagonIndexCount, [], :int + # @!method cell_area_rads2 + # + # Area of a given cell expressed in radians squared + # + # @example Return the area of the cell + # H3.cell_area_rads2(617700169958293503) + # 2.6952182709835757e-09 + # + # @return [Double] Area of cell in rads2 + attach_function :cell_area_rads2, :cellAreaRads2, %i[h3_index], :double + + # @!method cell_area_km2 + # + # Area of a given cell expressed in km squared + # + # @example Return the area of the cell + # H3.cell_area_km2(617700169958293503) + # 0.10939818864648902 + # + # @return [Double] Area of cell in km2 + attach_function :cell_area_km2, :cellAreaKm2, %i[h3_index], :double + + # @!method cell_area_m2 + # + # Area of a given cell expressed in metres squared + # + # @example Return the area of the cell + # H3.cell_area_m2(617700169958293503) + # 109398.18864648901 + # + # @return [Double] Area of cell in metres squared + attach_function :cell_area_m2, :cellAreaM2, %i[h3_index], :double + + # @!method exact_edge_length_rads + # + # Exact length of edge in rads + # + # @example Return the edge length + # H3.exact_edge_length_rads(1266218516299644927) + # 3.287684056071637e-05 + # + # @return [Double] Edge length in rads + attach_function :exact_edge_length_rads, :exactEdgeLengthRads, %i[h3_index], :double + + # @!method exact_edge_length_km + # + # Exact length of edge in kilometres + # + # @example Return the edge length + # H3.exact_edge_length_km(1266218516299644927) + # 3.287684056071637e-05 + # + # @return [Double] Edge length in kilometres + attach_function :exact_edge_length_km, :exactEdgeLengthKm, %i[h3_index], :double + + # @!method exact_edge_length_m + # + # Exact length of edge in metres + # + # @example Return the edge length + # H3.exact_edge_length_m(1266218516299644927) + # 3.287684056071637e-05 + # + # @return [Double] Edge length in metres + attach_function :exact_edge_length_m, :exactEdgeLengthM, %i[h3_index], :double + + # Returns the radians distance between two points. + # + # @example Return radians distance. + # H3.point_distance_rads([41.3964809, 2.160444], [41.3870609, 2.164917]) + # 0.00017453024784008713 + # + # @return [Double] Radians distance between two points. + def point_distance_rads(origin, destination) + Bindings::Private.point_distance_rads(*build_geocoords(origin, destination)) + end + + # Returns the kilometres distance between two points. + # + # @example Return km distance. + # H3.point_distance_km([41.3964809, 2.160444], [41.3870609, 2.164917]) + # 1.1119334622766763 + # + # @return [Double] KM distance between two points. + def point_distance_km(origin, destination) + Bindings::Private.point_distance_km(*build_geocoords(origin, destination)) + end + + # Returns the metre distance between two points. + # + # @example Return metre distance. + # H3.point_distance_m([41.3964809, 2.160444], [41.3870609, 2.164917]) + # 1111.9334622766764 + # + # @return [Double] Metre distance between two points. + def point_distance_m(origin, destination) + Bindings::Private.point_distance_m(*build_geocoords(origin, destination)) + end + # Returns all resolution 0 hexagons (base cells). # # @example Return all base cells. @@ -144,5 +243,29 @@ def pentagons(resolution) Bindings::Private.get_pentagon_indexes(resolution, out) out.read end + + private + + def build_geocoords(origin, destination) + [origin, destination].inject([]) do |acc, coords| + validate_coordinate(coords) + + geo_coord = GeoCoord.new + lat, lon = coords + geo_coord[:lat] = degs_to_rads(lat) + geo_coord[:lon] = degs_to_rads(lon) + acc << geo_coord + end + end + + def validate_coordinate(coords) + raise ArgumentError unless coords.is_a?(Array) && coords.count == 2 + + lat, lon = coords + + if lat > 90 || lat < -90 || lon > 180 || lon < -180 + raise(ArgumentError, "Invalid coordinates") + end + end end end diff --git a/lib/h3/version.rb b/lib/h3/version.rb index d9c594e..e457878 100644 --- a/lib/h3/version.rb +++ b/lib/h3/version.rb @@ -1,3 +1,3 @@ module H3 - VERSION = "3.6.4".freeze + VERSION = "3.7.1".freeze end diff --git a/spec/miscellaneous_spec.rb b/spec/miscellaneous_spec.rb index 6c3443c..def6473 100644 --- a/spec/miscellaneous_spec.rb +++ b/spec/miscellaneous_spec.rb @@ -109,4 +109,121 @@ 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