///|
/// A latitude/longitude pair in decimal degrees.
pub struct LatLng {
latitude : Double
longitude : Double
} derive(Eq, Debug)
///|
pub fn LatLng::latitude(self : LatLng) -> Double {
self.latitude
}
///|
pub fn LatLng::longitude(self : LatLng) -> Double {
self.longitude
}
///|
priv struct Vec3(Double, Double, Double)
///|
priv struct CoordIJK(Int, Int, Int)
///|
priv struct FaceIJK(Int, CoordIJK)
///|
priv struct FaceOrientIJK(Int, CoordIJK, Int)
///|
priv enum Overage {
NoOverage
NewFace
} derive(Eq)
///|
const TWO_PI : Double = 6.283185307179586476925286766559005768
///|
const DEGREES_PER_RADIAN : Double = 57.295779513082320876798154814105170332
///|
const EPSILON : Double = 1.0e-16
///|
const SQRT3_OVER_2 : Double = 0.866025403784438646763723170752936183
///|
const AP7_ROT_RADS : Double = 0.333473172251832115336090755351601070
///|
const RES0_U_GNOMONIC : Double = 0.38196601125010500003
///|
const INVERSE_SQRT7 : Double = 0.3779644730092272272145165362341800608
///|
let face_center_points : Array[Vec3] = [
Vec3(0.2199307791404606, 0.6583691780274996, 0.7198475378926182),
Vec3(-0.2139234834501421, 0.1478171829550703, 0.9656017935214205),
Vec3(0.1092625278784797, -0.4811951572873210, 0.8697775121287253),
Vec3(0.7428567301586791, -0.3593941678278028, 0.5648005936517033),
Vec3(0.8112534709140969, 0.3448953237639384, 0.4721387736413930),
Vec3(-0.1055498149613921, 0.9794457296411413, 0.1718874610009365),
Vec3(-0.8075407579970092, 0.1533552485898818, 0.5695261994882688),
Vec3(-0.2846148069787907, -0.8644080972654206, 0.4144792552473539),
Vec3(0.7405621473854482, -0.6673299564565524, -0.0789837646326737),
Vec3(0.8512303986474293, 0.4722343788582681, -0.2289137388687808),
Vec3(-0.7405621473854481, 0.6673299564565524, 0.0789837646326737),
Vec3(-0.8512303986474292, -0.4722343788582682, 0.2289137388687808),
Vec3(0.1055498149613919, -0.9794457296411413, -0.1718874610009365),
Vec3(0.8075407579970092, -0.1533552485898819, -0.5695261994882688),
Vec3(0.2846148069787908, 0.8644080972654204, -0.4144792552473539),
Vec3(-0.7428567301586791, 0.3593941678278027, -0.5648005936517033),
Vec3(-0.8112534709140971, -0.3448953237639382, -0.4721387736413930),
Vec3(-0.2199307791404607, -0.6583691780274996, -0.7198475378926182),
Vec3(0.2139234834501420, -0.1478171829550704, -0.9656017935214205),
Vec3(-0.1092625278784796, 0.4811951572873210, -0.8697775121287253),
]
///|
let face_axes_azimuth : Array[Double] = [
5.619958268523939882, 5.760339081714187279, 0.780213654393430055, 0.430469363979999913,
6.130269123335111400, 2.692877706530642877, 2.982963003477243874, 3.532912002790141181,
3.494305004259568154, 3.003214169499538391, 5.930472956509811562, 0.138378484090254847,
0.448714947059150361, 0.158629650112549365, 5.891865957979238535, 2.711123289609793325,
3.294508837434268316, 3.804819692245439833, 3.664438879055192436, 2.361378999196363184,
]
///|
let base_cell_home : Array[(Int, Int, Int, Int)] = [
(1, 1, 0, 0),
(2, 1, 1, 0),
(1, 0, 0, 0),
(2, 1, 0, 0),
(0, 2, 0, 0),
(1, 1, 1, 0),
(1, 0, 0, 1),
(2, 0, 0, 0),
(0, 1, 0, 0),
(2, 0, 1, 0),
(1, 0, 1, 0),
(1, 0, 1, 1),
(3, 1, 0, 0),
(3, 1, 1, 0),
(11, 2, 0, 0),
(4, 1, 0, 0),
(0, 0, 0, 0),
(6, 0, 1, 0),
(0, 0, 0, 1),
(2, 0, 1, 1),
(7, 0, 0, 1),
(2, 0, 0, 1),
(0, 1, 1, 0),
(6, 0, 0, 1),
(10, 2, 0, 0),
(6, 0, 0, 0),
(3, 0, 0, 0),
(11, 1, 0, 0),
(4, 1, 1, 0),
(3, 0, 1, 0),
(0, 0, 1, 1),
(4, 0, 0, 0),
(5, 0, 1, 0),
(0, 0, 1, 0),
(7, 0, 1, 0),
(11, 1, 1, 0),
(7, 0, 0, 0),
(10, 1, 0, 0),
(12, 2, 0, 0),
(6, 1, 0, 1),
(7, 1, 0, 1),
(4, 0, 0, 1),
(3, 0, 0, 1),
(3, 0, 1, 1),
(4, 0, 1, 0),
(6, 1, 0, 0),
(11, 0, 0, 0),
(8, 0, 0, 1),
(5, 0, 0, 1),
(14, 2, 0, 0),
(5, 0, 0, 0),
(12, 1, 0, 0),
(10, 1, 1, 0),
(4, 0, 1, 1),
(12, 1, 1, 0),
(7, 1, 0, 0),
(11, 0, 1, 0),
(10, 0, 0, 0),
(13, 2, 0, 0),
(10, 0, 0, 1),
(11, 0, 0, 1),
(9, 0, 1, 0),
(8, 0, 1, 0),
(6, 2, 0, 0),
(8, 0, 0, 0),
(9, 0, 0, 1),
(14, 1, 0, 0),
(5, 1, 0, 1),
(16, 0, 1, 1),
(8, 1, 0, 1),
(5, 1, 0, 0),
(12, 0, 0, 0),
(7, 2, 0, 0),
(12, 0, 1, 0),
(10, 0, 1, 0),
(9, 0, 0, 0),
(13, 1, 0, 0),
(16, 0, 0, 1),
(15, 0, 1, 1),
(15, 0, 1, 0),
(16, 0, 1, 0),
(14, 1, 1, 0),
(13, 1, 1, 0),
(5, 2, 0, 0),
(8, 1, 0, 0),
(14, 0, 0, 0),
(9, 1, 0, 1),
(14, 0, 0, 1),
(17, 0, 0, 1),
(12, 0, 0, 1),
(16, 0, 0, 0),
(17, 0, 1, 1),
(15, 0, 0, 1),
(16, 1, 0, 1),
(9, 1, 0, 0),
(15, 0, 0, 0),
(13, 0, 0, 0),
(8, 2, 0, 0),
(13, 0, 1, 0),
(17, 1, 0, 1),
(19, 0, 1, 0),
(14, 0, 1, 0),
(19, 0, 1, 1),
(17, 0, 1, 0),
(13, 0, 0, 1),
(17, 0, 0, 0),
(16, 1, 0, 0),
(9, 2, 0, 0),
(15, 1, 0, 1),
(15, 1, 0, 0),
(18, 0, 1, 1),
(18, 0, 0, 1),
(19, 0, 0, 1),
(17, 1, 0, 0),
(19, 0, 0, 0),
(18, 0, 1, 0),
(18, 1, 0, 1),
(19, 2, 0, 0),
(19, 1, 0, 0),
(18, 0, 0, 0),
(19, 1, 0, 1),
(18, 1, 0, 0),
]
///|
let face_neighbors : Array[Array[FaceOrientIJK]] = [
[
FaceOrientIJK(0, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(4, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(1, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(5, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(1, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(0, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(2, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(6, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(2, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(1, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(3, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(7, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(3, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(2, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(4, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(8, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(4, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(3, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(0, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(9, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(5, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(10, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(14, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(0, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(6, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(11, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(10, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(1, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(7, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(12, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(11, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(2, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(8, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(13, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(12, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(3, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(9, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(14, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(13, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(4, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(10, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(5, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(6, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(15, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(11, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(6, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(7, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(16, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(12, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(7, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(8, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(17, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(13, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(8, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(9, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(18, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(14, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(9, CoordIJK(2, 2, 0), 3),
FaceOrientIJK(5, CoordIJK(2, 0, 2), 3),
FaceOrientIJK(19, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(15, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(16, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(19, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(10, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(16, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(17, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(15, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(11, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(17, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(18, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(16, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(12, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(18, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(19, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(17, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(13, CoordIJK(0, 2, 2), 3),
],
[
FaceOrientIJK(19, CoordIJK(0, 0, 0), 0),
FaceOrientIJK(15, CoordIJK(2, 0, 2), 1),
FaceOrientIJK(18, CoordIJK(2, 2, 0), 5),
FaceOrientIJK(14, CoordIJK(0, 2, 2), 3),
],
]
///|
let max_dimension : Array[Int] = [
2, 0, 14, 0, 98, 0, 686, 0, 4802, 0, 33614, 0, 235298, 0, 1647086, 0, 11529602,
]
///|
let unit_scale : Array[Int] = [
1, 0, 7, 0, 49, 0, 343, 0, 2401, 0, 16807, 0, 117649, 0, 823543, 0, 5764801,
]
///|
fn is_resolution_class_iii(resolution : Int) -> Bool {
resolution % 2 == 1
}
///|
fn leading_nonzero_digit(value : UInt64) -> Int {
let mut current = 1
while current <= resolution_of(value) {
let digit = digit_of(value, current)
if digit != 0 {
return digit
}
current += 1
}
0
}
///|
fn rotate_digit_60cw(digit : Int) -> Int {
match digit {
1 => 3
3 => 2
2 => 6
6 => 4
4 => 5
5 => 1
_ => digit
}
}
///|
fn set_digit(value : UInt64, resolution : Int, digit : Int) -> UInt64 {
let shift = (MAX_RESOLUTION - resolution) * 3
let mask = 7UL << shift
(value & UInt64::lnot(mask)) | (digit.to_uint64() << shift)
}
///|
fn rotate_index_60cw(value : UInt64) -> UInt64 {
let mut rotated = value
let mut current = 1
while current <= resolution_of(value) {
rotated = set_digit(
rotated,
current,
rotate_digit_60cw(digit_of(rotated, current)),
)
current += 1
}
rotated
}
///|
fn coord(i : Int, j : Int, k : Int) -> CoordIJK {
CoordIJK(i, j, k)
}
///|
fn coord_add(left : CoordIJK, right : CoordIJK) -> CoordIJK {
CoordIJK(left.0 + right.0, left.1 + right.1, left.2 + right.2)
}
///|
fn coord_sub(left : CoordIJK, right : CoordIJK) -> CoordIJK {
CoordIJK(left.0 - right.0, left.1 - right.1, left.2 - right.2)
}
///|
fn coord_scale(value : CoordIJK, factor : Int) -> CoordIJK {
CoordIJK(value.0 * factor, value.1 * factor, value.2 * factor)
}
///|
fn coord_normalize(value : CoordIJK) -> CoordIJK {
let mut i = value.0
let mut j = value.1
let mut k = value.2
if i < 0 {
j -= i
k -= i
i = 0
}
if j < 0 {
i -= j
k -= j
j = 0
}
if k < 0 {
i -= k
j -= k
k = 0
}
let minimum = Int::min(i, Int::min(j, k))
if minimum > 0 {
i -= minimum
j -= minimum
k -= minimum
}
CoordIJK(i, j, k)
}
///|
fn coord_down_ap7(value : CoordIJK) -> CoordIJK {
let i_vec = coord_scale(coord(3, 0, 1), value.0)
let j_vec = coord_scale(coord(1, 3, 0), value.1)
let k_vec = coord_scale(coord(0, 1, 3), value.2)
coord_normalize(coord_add(coord_add(i_vec, j_vec), k_vec))
}
///|
fn coord_down_ap7r(value : CoordIJK) -> CoordIJK {
let i_vec = coord_scale(coord(3, 1, 0), value.0)
let j_vec = coord_scale(coord(0, 3, 1), value.1)
let k_vec = coord_scale(coord(1, 0, 3), value.2)
coord_normalize(coord_add(coord_add(i_vec, j_vec), k_vec))
}
///|
fn coord_up_ap7r(value : CoordIJK) -> CoordIJK {
let i = value.0 - value.2
let j = value.1 - value.2
coord_normalize(
CoordIJK(
((2 * i + j).to_double() / 7.0).round().to_int(),
((3 * j - i).to_double() / 7.0).round().to_int(),
0,
),
)
}
///|
fn coord_rotate_60ccw(value : CoordIJK) -> CoordIJK {
let i_vec = coord_scale(coord(1, 1, 0), value.0)
let j_vec = coord_scale(coord(0, 1, 1), value.1)
let k_vec = coord_scale(coord(1, 0, 1), value.2)
coord_normalize(coord_add(coord_add(i_vec, j_vec), k_vec))
}
///|
fn coord_rotate_60cw(value : CoordIJK) -> CoordIJK {
let i_vec = coord_scale(coord(1, 0, 1), value.0)
let j_vec = coord_scale(coord(1, 1, 0), value.1)
let k_vec = coord_scale(coord(0, 1, 1), value.2)
coord_normalize(coord_add(coord_add(i_vec, j_vec), k_vec))
}
///|
fn coord_neighbor(value : CoordIJK, digit : Int) -> CoordIJK {
let unit = match digit {
1 => coord(0, 0, 1)
2 => coord(0, 1, 0)
3 => coord(0, 1, 1)
4 => coord(1, 0, 0)
5 => coord(1, 0, 1)
6 => coord(1, 1, 0)
_ => coord(0, 0, 0)
}
coord_normalize(coord_add(value, unit))
}
///|
fn home_face_ijk(base_cell : Int) -> FaceIJK {
let (face, i, j, k) = base_cell_home[base_cell]
FaceIJK(face, coord(i, j, k))
}
///|
fn adjust_overage_class_ii(
face_ijk : FaceIJK,
resolution : Int,
pentagon_leading_4 : Bool,
) -> (FaceIJK, Overage) {
let maximum = max_dimension[resolution]
let current = face_ijk.1
if current.0 + current.1 + current.2 <= maximum {
return (face_ijk, NoOverage)
}
let mut index = current
let quadrant = if index.2 > 0 {
if index.1 > 0 {
3
} else {
if pentagon_leading_4 {
let origin = coord(maximum, 0, 0)
index = coord_add(coord_rotate_60cw(coord_sub(index, origin)), origin)
}
2
}
} else {
1
}
let orientation = face_neighbors[face_ijk.0][quadrant]
let mut rotated = index
let mut rotation = 0
while rotation < orientation.2 {
rotated = coord_rotate_60ccw(rotated)
rotation += 1
}
let translated = coord_add(
rotated,
coord_scale(orientation.1, unit_scale[resolution]),
)
(FaceIJK(orientation.0, coord_normalize(translated)), NewFace)
}
///|
fn index_to_face_ijk(value : UInt64) -> FaceIJK {
let base_cell = base_cell_of(value)
let pentagon = is_pentagon_base_cell(base_cell)
let leading = leading_nonzero_digit(value)
let adjusted = if pentagon && leading == 5 {
rotate_index_60cw(value)
} else {
value
}
let home = home_face_ijk(base_cell)
let resolution = resolution_of(adjusted)
let mut index = home.1
let mut current = 1
while current <= resolution {
index = if is_resolution_class_iii(current) {
coord_down_ap7(index)
} else {
coord_down_ap7r(index)
}
index = coord_neighbor(index, digit_of(adjusted, current))
current += 1
}
let possible_overage = if !pentagon &&
(resolution == 0 || (home.1.0 == 0 && home.1.1 == 0 && home.1.2 == 0)) {
false
} else {
true
}
if !possible_overage {
return FaceIJK(home.0, index)
}
let original = index
let mut face = home.0
let mut adjusted_resolution = resolution
if is_resolution_class_iii(adjusted_resolution) {
index = coord_down_ap7r(index)
adjusted_resolution += 1
}
let pentagon_leading_4 = pentagon && leading == 4
let (adjusted_face, overage) = adjust_overage_class_ii(
FaceIJK(face, index),
adjusted_resolution,
pentagon_leading_4,
)
face = adjusted_face.0
index = adjusted_face.1
if overage != NoOverage {
if pentagon {
let mut more = true
while more {
let (next_face, next_overage) = adjust_overage_class_ii(
FaceIJK(face, index),
adjusted_resolution,
false,
)
face = next_face.0
index = next_face.1
more = next_overage != NoOverage
}
}
if adjusted_resolution != resolution {
index = coord_up_ap7r(index)
}
} else if adjusted_resolution != resolution {
index = original
}
FaceIJK(face, index)
}
///|
fn vec3_add(left : Vec3, right : Vec3) -> Vec3 {
Vec3(left.0 + right.0, left.1 + right.1, left.2 + right.2)
}
///|
fn vec3_scale(value : Vec3, factor : Double) -> Vec3 {
Vec3(value.0 * factor, value.1 * factor, value.2 * factor)
}
///|
fn vec3_linear_combination(
left_factor : Double,
left : Vec3,
right_factor : Double,
right : Vec3,
) -> Vec3 {
vec3_add(vec3_scale(left, left_factor), vec3_scale(right, right_factor))
}
///|
fn vec3_dot(left : Vec3, right : Vec3) -> Double {
left.0 * right.0 + left.1 * right.1 + left.2 * right.2
}
///|
fn vec3_cross(left : Vec3, right : Vec3) -> Vec3 {
Vec3(
left.1 * right.2 - left.2 * right.1,
left.2 * right.0 - left.0 * right.2,
left.0 * right.1 - left.1 * right.0,
)
}
///|
fn vec3_normalize(value : Vec3) -> Vec3 {
let norm = vec3_dot(value, value).sqrt()
let factor = if norm > 0.0 { 1.0 / norm } else { 0.0 }
vec3_scale(value, factor)
}
///|
fn positive_angle(radians : Double) -> Double {
let mut angle = radians
if angle < 0.0 {
angle += TWO_PI
}
if angle >= TWO_PI {
angle -= TWO_PI
}
angle
}
///|
fn ijk_to_hex_2d(value : CoordIJK) -> (Double, Double) {
let i = value.0 - value.2
let j = value.1 - value.2
(i.to_double() - 0.5 * j.to_double(), j.to_double() * SQRT3_OVER_2)
}
///|
fn tangent_basis(point : Vec3) -> (Vec3, Vec3) {
let north_pole = Vec3(0.0, 0.0, 1.0)
let north = vec3_normalize(
vec3_linear_combination(
1.0,
north_pole,
-vec3_dot(north_pole, point),
point,
),
)
let east = vec3_cross(north, point)
(north, east)
}
///|
fn hex_2d_to_vec3(x : Double, y : Double, face : Int, resolution : Int) -> Vec3 {
let mut radius = (x * x + y * y).sqrt()
if radius < EPSILON {
return face_center_points[face]
}
let mut theta = @math.atan2(y, x)
let mut current = 0
while current < resolution {
radius *= INVERSE_SQRT7
current += 1
}
radius *= RES0_U_GNOMONIC
radius = @math.atan(radius)
if is_resolution_class_iii(resolution) {
theta = positive_angle(theta + AP7_ROT_RADS)
}
theta = positive_angle(face_axes_azimuth[face] - theta)
let center = face_center_points[face]
let (north, east) = tangent_basis(center)
let direction = vec3_linear_combination(
@math.cos(theta),
north,
@math.sin(theta),
east,
)
vec3_normalize(
vec3_linear_combination(
@math.cos(radius),
center,
@math.sin(radius),
direction,
),
)
}
///|
fn face_ijk_to_vec3(face_ijk : FaceIJK, resolution : Int) -> Vec3 {
let (x, y) = ijk_to_hex_2d(face_ijk.1)
hex_2d_to_vec3(x, y, face_ijk.0, resolution)
}
///|
/// Returns the center point of this H3 cell in decimal degrees.
pub fn H3Index::to_lat_lng(self : H3Index) -> LatLng {
let center = face_ijk_to_vec3(index_to_face_ijk(self.0), self.resolution())
{
latitude: @math.asin(center.2) * DEGREES_PER_RADIAN,
longitude: @math.atan2(center.1, center.0) * DEGREES_PER_RADIAN,
}
}