lib/h3/regions.rb
module H3
# Region functions.
#
# @see https://uber.github.io/h3/#/documentation/api-reference/regions
module Regions
extend H3::Bindings::Base
# Derive the maximum number of H3 indexes that could be returned from the input.
#
# @param [String, Array<Array<Array<Float>>>] geo_polygon Either a GeoJSON string
# or a coordinates nested array.
# @param [Integer] resolution Resolution.
#
# @example Derive maximum number of hexagons for given GeoJSON document.
# geo_json = "{\"type\":\"Polygon\",\"coordinates\":[[[-1.735839843749998,52.24630137198303],
# [-1.8923950195312498,52.05249047600099],[-1.56829833984375,51.891749018068246],
# [-1.27716064453125,51.91208502557545],[-1.19476318359375,52.032218104145294],
# [-1.24420166015625,52.19413974159753],[-1.5902709960937498,52.24125614966341],
# [-1.7358398437499998,52.24630137198303]],[[-1.58203125,52.12590076522272],
# [-1.476287841796875,52.12590076522272],[-1.46392822265625,52.075285904832334],
# [-1.58203125,52.06937709602395],[-1.58203125,52.12590076522272]],
# [[-1.4556884765625,52.01531743663362],[-1.483154296875,51.97642166216334],
# [-1.3677978515625,51.96626938051444],[-1.3568115234375,52.0102459910103],
# [-1.4556884765625,52.01531743663362]]]}"
# H3.max_polyfill_size(geo_json, 9)
# 33391
#
# @example Derive maximum number of hexagons for a nested array of coordinates.
# coordinates = [
# [
# [52.24630137198303, -1.7358398437499998], [52.05249047600099, -1.8923950195312498],
# [51.891749018068246, -1.56829833984375], [51.91208502557545, -1.27716064453125],
# [52.032218104145294, -1.19476318359375], [52.19413974159753, -1.24420166015625],
# [52.24125614966341, -1.5902709960937498], [52.24630137198303, -1.7358398437499998]
# ],
# [
# [52.12590076522272, -1.58203125], [52.12590076522272, -1.476287841796875],
# [52.075285904832334, -1.46392822265625], [52.06937709602395, -1.58203125],
# [52.12590076522272, -1.58203125]
# ],
# [
# [52.01531743663362, -1.4556884765625], [51.97642166216334, -1.483154296875],
# [51.96626938051444, -1.3677978515625], [52.0102459910103, -1.3568115234375],
# [52.01531743663362, -1.4556884765625]
# ]
# ]
# H3.max_polyfill_size(coordinates, 9)
# 33391
#
# @return [Integer] Maximum number of hexagons needed to polyfill given area.
def max_polyfill_size(geo_polygon, resolution)
geo_polygon = geo_json_to_coordinates(geo_polygon) if geo_polygon.is_a?(String)
Bindings::Private.max_polyfill_size(build_polygon(geo_polygon), resolution)
end
# Derive a list of H3 indexes that fall within a given geo polygon structure.
#
# @param [String, Array<Array<Array<Float>>>] geo_polygon Either a GeoJSON string or a
# coordinates nested array.
# @param [Integer] resolution Resolution.
#
# @example Derive hexagons for given GeoJSON document.
# geo_json = "{\"type\":\"Polygon\",\"coordinates\":[[[-1.735839843799998,52.24630137198303],
# [-1.8923950195312498,52.05249047600099],[-1.56829833984375,51.891749018068246],
# [-1.27716064453125,51.91208502557545],[-1.19476318359375,52.032218104145294],
# [-1.24420166015625,52.19413974159753],[-1.5902709960937498,52.24125614966341],
# [-1.7358398437499998,52.24630137198303]],[[-1.58203125,52.12590076522272],
# [-1.476287841796875,52.12590076522272],[-1.46392822265625,52.075285904832334],
# [-1.58203125,52.06937709602395],[-1.58203125,52.12590076522272]],
# [[-1.4556884765625,52.01531743663362],[-1.483154296875,51.97642166216334],
# [-1.3677978515625,51.96626938051444],[-1.3568115234375,52.0102459910103],
# [-1.4556884765625,52.01531743663362]]]}"
# H3.polyfill(geo_json, 5)
# [
# 599424968551301119, 599424888020664319, 599424970698784767, 599424964256333823,
# 599424969625042943, 599425001837297663, 599425000763555839
# ]
#
# @example Derive hexagons for a nested array of coordinates.
# coordinates = [
# [
# [52.24630137198303, -1.7358398437499998], [52.05249047600099, -1.8923950195312498],
# [51.891749018068246, -1.56829833984375], [51.91208502557545, -1.27716064453125],
# [52.032218104145294, -1.19476318359375], [52.19413974159753, -1.24420166015625],
# [52.24125614966341, -1.5902709960937498], [52.24630137198303, -1.7358398437499998]
# ],
# [
# [52.12590076522272, -1.58203125], [52.12590076522272, -1.476287841796875],
# [52.075285904832334, -1.46392822265625], [52.06937709602395, -1.58203125],
# [52.12590076522272, -1.58203125]
# ],
# [
# [52.01531743663362, -1.4556884765625], [51.97642166216334, -1.483154296875],
# [51.96626938051444, -1.3677978515625], [52.0102459910103, -1.3568115234375],
# [52.01531743663362, -1.4556884765625]
# ]
# ]
# H3.polyfill(coordinates, 5)
# [
# 599424968551301119, 599424888020664319, 599424970698784767, 599424964256333823,
# 599424969625042943, 599425001837297663, 599425000763555839
# ]
#
# @return [Array<Integer>] Hexagons needed to polyfill given area.
def polyfill(geo_polygon, resolution)
geo_polygon = geo_json_to_coordinates(geo_polygon) if geo_polygon.is_a?(String)
max_size = max_polyfill_size(geo_polygon, resolution)
out = H3Indexes.of_size(max_size)
Bindings::Private.polyfill(build_polygon(geo_polygon), resolution, out)
out.read
end
# Derive a nested array of coordinates from a list of H3 indexes.
#
# @param [Array<Integer>] h3_indexes A list of H3 indexes.
#
# @example Get a set of coordinates from a given list of H3 indexes.
# h3_indexes = [
# 599424968551301119, 599424888020664319, 599424970698784767,
# 599424964256333823, 599424969625042943, 599425001837297663,
# 599425000763555839
# ]
# H3.h3_set_to_linked_geo(h3_indexes)
# [
# [
# [52.24425364171531, -1.6470570189756442], [52.19515282473624, -1.7508281227260887],
# [52.10973325363767, -1.7265910686763437], [52.06042870859474, -1.8301115887419024],
# [51.97490199314513, -1.8057974545517919], [51.9387204737266, -1.6783497689296265],
# [51.853128001893175, -1.654344796003053], [51.81682604752331, -1.5274195136674955],
# [51.866019925789956, -1.424329996292339], [51.829502535462176, -1.2977583914075301],
# [51.87843896218677, -1.1946402363628545], [51.96394676922824, -1.21787542551618],
# [52.01267958543637, -1.1145114691876956], [52.09808058649905, -1.1376655003242908],
# [52.134791926560325, -1.26456988729442], [52.22012854584846, -1.2880298658365215],
# [52.25672060485973, -1.4154623025177386], [52.20787927927604, -1.5192658757247421]
# ]
# ]
#
# @return [Array<Array<Array<Float>>>] Nested array of coordinates.
def h3_set_to_linked_geo(h3_indexes)
h3_set = H3Indexes.with_contents(h3_indexes)
linked_geo_polygon = LinkedGeoPolygon.new
Bindings::Private.h3_set_to_linked_geo(h3_set, h3_indexes.size, linked_geo_polygon)
# The algorithm in h3 currently only handles 1 polygon
extract_linked_geo_polygon(linked_geo_polygon).first
ensure
Bindings::Private.destroy_linked_polygon(linked_geo_polygon)
end
private
def extract_linked_geo_polygon(linked_geo_polygon)
return if linked_geo_polygon.null?
geo_polygons = [linked_geo_polygon]
until linked_geo_polygon[:next].null?
# Until the h3 algorithm is updated to handle multiple polygons,
# this block will never run.
geo_polygons << linked_geo_polygon[:next]
linked_geo_polygon = linked_geo_polygon[:next]
end
geo_polygons.map(&method(:extract_geo_polygon))
end
def extract_geo_polygon(geo_polygon)
extract_linked_geo_loop(geo_polygon[:first]) unless geo_polygon[:first].null?
end
def extract_linked_geo_loop(linked_geo_loop)
return if linked_geo_loop.null?
geo_loops = [linked_geo_loop]
until linked_geo_loop[:next].null?
geo_loops << linked_geo_loop[:next]
linked_geo_loop = linked_geo_loop[:next]
end
geo_loops.map(&method(:extract_geo_loop))
end
def extract_geo_loop(geo_loop)
extract_linked_geo_coord(geo_loop[:first]) unless geo_loop[:first].null?
end
def extract_linked_geo_coord(linked_geo_coord)
return if linked_geo_coord.null?
geo_coords = [linked_geo_coord]
until linked_geo_coord[:next].null?
geo_coords << linked_geo_coord[:next]
linked_geo_coord = linked_geo_coord[:next]
end
geo_coords.map(&method(:extract_geo_coord))
end
def extract_geo_coord(geo_coord)
[
rads_to_degs(geo_coord[:vertex][:lat]),
rads_to_degs(geo_coord[:vertex][:lon])
]
end
def build_polygon(input)
outline, *holes = input
geo_polygon = GeoPolygon.new
geo_polygon[:geofence] = build_geofence(outline)
len = holes.count
geo_polygon[:num_holes] = len
geofences = holes.map(&method(:build_geofence))
ptr = FFI::MemoryPointer.new(GeoFence, len)
fence_structs = 0.upto(geofences.count).map do |i|
GeoFence.new(ptr + i * GeoFence.size)
end
geofences.each_with_index do |geofence, i|
fence_structs[i][:num_verts] = geofence[:num_verts]
fence_structs[i][:verts] = geofence[:verts]
end
geo_polygon[:holes] = ptr
geo_polygon
end
def build_geofence(input)
geo_fence = GeoFence.new
len = input.count
geo_fence[:num_verts] = len
ptr = FFI::MemoryPointer.new(GeoCoord, len)
coords = 0.upto(len).map do |i|
GeoCoord.new(ptr + i * GeoCoord.size)
end
input.each_with_index do |(lat, lon), i|
coords[i][:lat] = degs_to_rads(lat)
coords[i][:lon] = degs_to_rads(lon)
end
geo_fence[:verts] = ptr
geo_fence
end
end
end