///|
const INVERSE_SIN_60 : Double = 1.1547005383792515290182975610039149113
///|
const INVERSE_RES0_U_GNOMONIC : Double = 2.61803398874989588842
///|
const SQRT7 : Double = 2.6457513110645905905016157536392604257
///|
let face_ijk_base_cells : Array[(Int, Int)] = [
// face 0
(16, 0),
(18, 0),
(24, 0),
(33, 0),
(30, 0),
(32, 3),
(49, 1),
(48, 3),
(50, 3),
(8, 0),
(5, 5),
(10, 5),
(22, 0),
(16, 0),
(18, 0),
(41, 1),
(33, 0),
(30, 0),
(4, 0),
(0, 5),
(2, 5),
(15, 1),
(8, 0),
(5, 5),
(31, 1),
(22, 0),
(16, 0),
// face 1
(2, 0),
(6, 0),
(14, 0),
(10, 0),
(11, 0),
(17, 3),
(24, 1),
(23, 3),
(25, 3),
(0, 0),
(1, 5),
(9, 5),
(5, 0),
(2, 0),
(6, 0),
(18, 1),
(10, 0),
(11, 0),
(4, 1),
(3, 5),
(7, 5),
(8, 1),
(0, 0),
(1, 5),
(16, 1),
(5, 0),
(2, 0),
// face 2
(7, 0),
(21, 0),
(38, 0),
(9, 0),
(19, 0),
(34, 3),
(14, 1),
(20, 3),
(36, 3),
(3, 0),
(13, 5),
(29, 5),
(1, 0),
(7, 0),
(21, 0),
(6, 1),
(9, 0),
(19, 0),
(4, 2),
(12, 5),
(26, 5),
(0, 1),
(3, 0),
(13, 5),
(2, 1),
(1, 0),
(7, 0),
// face 3
(26, 0),
(42, 0),
(58, 0),
(29, 0),
(43, 0),
(62, 3),
(38, 1),
(47, 3),
(64, 3),
(12, 0),
(28, 5),
(44, 5),
(13, 0),
(26, 0),
(42, 0),
(21, 1),
(29, 0),
(43, 0),
(4, 3),
(15, 5),
(31, 5),
(3, 1),
(12, 0),
(28, 5),
(7, 1),
(13, 0),
(26, 0),
// face 4
(31, 0),
(41, 0),
(49, 0),
(44, 0),
(53, 0),
(61, 3),
(58, 1),
(65, 3),
(75, 3),
(15, 0),
(22, 5),
(33, 5),
(28, 0),
(31, 0),
(41, 0),
(42, 1),
(44, 0),
(53, 0),
(4, 4),
(8, 5),
(16, 5),
(12, 1),
(15, 0),
(22, 5),
(26, 1),
(28, 0),
(31, 0),
// face 5
(50, 0),
(48, 0),
(49, 3),
(32, 0),
(30, 3),
(33, 3),
(24, 3),
(18, 3),
(16, 3),
(70, 0),
(67, 0),
(66, 3),
(52, 3),
(50, 0),
(48, 0),
(37, 3),
(32, 0),
(30, 3),
(83, 0),
(87, 3),
(85, 3),
(74, 3),
(70, 0),
(67, 0),
(57, 1),
(52, 3),
(50, 0),
// face 6
(25, 0),
(23, 0),
(24, 3),
(17, 0),
(11, 3),
(10, 3),
(14, 3),
(6, 3),
(2, 3),
(45, 0),
(39, 0),
(37, 3),
(35, 3),
(25, 0),
(23, 0),
(27, 3),
(17, 0),
(11, 3),
(63, 0),
(59, 3),
(57, 3),
(56, 3),
(45, 0),
(39, 0),
(46, 3),
(35, 3),
(25, 0),
// face 7
(36, 0),
(20, 0),
(14, 3),
(34, 0),
(19, 3),
(9, 3),
(38, 3),
(21, 3),
(7, 3),
(55, 0),
(40, 0),
(27, 3),
(54, 3),
(36, 0),
(20, 0),
(51, 3),
(34, 0),
(19, 3),
(72, 0),
(60, 3),
(46, 3),
(73, 3),
(55, 0),
(40, 0),
(71, 3),
(54, 3),
(36, 0),
// face 8
(64, 0),
(47, 0),
(38, 3),
(62, 0),
(43, 3),
(29, 3),
(58, 3),
(42, 3),
(26, 3),
(84, 0),
(69, 0),
(51, 3),
(82, 3),
(64, 0),
(47, 0),
(76, 3),
(62, 0),
(43, 3),
(97, 0),
(89, 3),
(71, 3),
(98, 3),
(84, 0),
(69, 0),
(96, 3),
(82, 3),
(64, 0),
// face 9
(75, 0),
(65, 0),
(58, 3),
(61, 0),
(53, 3),
(44, 3),
(49, 3),
(41, 3),
(31, 3),
(94, 0),
(86, 0),
(76, 3),
(81, 3),
(75, 0),
(65, 0),
(66, 3),
(61, 0),
(53, 3),
(107, 0),
(104, 3),
(96, 3),
(101, 3),
(94, 0),
(86, 0),
(85, 3),
(81, 3),
(75, 0),
// face 10
(57, 0),
(59, 0),
(63, 3),
(74, 0),
(78, 3),
(79, 3),
(83, 3),
(92, 3),
(95, 3),
(37, 0),
(39, 3),
(45, 3),
(52, 0),
(57, 0),
(59, 0),
(70, 3),
(74, 0),
(78, 3),
(24, 0),
(23, 3),
(25, 3),
(32, 3),
(37, 0),
(39, 3),
(50, 3),
(52, 0),
(57, 0),
// face 11
(46, 0),
(60, 0),
(72, 3),
(56, 0),
(68, 3),
(80, 3),
(63, 3),
(77, 3),
(90, 3),
(27, 0),
(40, 3),
(55, 3),
(35, 0),
(46, 0),
(60, 0),
(45, 3),
(56, 0),
(68, 3),
(14, 0),
(20, 3),
(36, 3),
(17, 3),
(27, 0),
(40, 3),
(25, 3),
(35, 0),
(46, 0),
// face 12
(71, 0),
(89, 0),
(97, 3),
(73, 0),
(91, 3),
(103, 3),
(72, 3),
(88, 3),
(105, 3),
(51, 0),
(69, 3),
(84, 3),
(54, 0),
(71, 0),
(89, 0),
(55, 3),
(73, 0),
(91, 3),
(38, 0),
(47, 3),
(64, 3),
(34, 3),
(51, 0),
(69, 3),
(36, 3),
(54, 0),
(71, 0),
// face 13
(96, 0),
(104, 0),
(107, 3),
(98, 0),
(110, 3),
(115, 3),
(97, 3),
(111, 3),
(119, 3),
(76, 0),
(86, 3),
(94, 3),
(82, 0),
(96, 0),
(104, 0),
(84, 3),
(98, 0),
(110, 3),
(58, 0),
(65, 3),
(75, 3),
(62, 3),
(76, 0),
(86, 3),
(64, 3),
(82, 0),
(96, 0),
// face 14
(85, 0),
(87, 0),
(83, 3),
(101, 0),
(102, 3),
(100, 3),
(107, 3),
(112, 3),
(114, 3),
(66, 0),
(67, 3),
(70, 3),
(81, 0),
(85, 0),
(87, 0),
(94, 3),
(101, 0),
(102, 3),
(49, 0),
(48, 3),
(50, 3),
(61, 3),
(66, 0),
(67, 3),
(75, 3),
(81, 0),
(85, 0),
// face 15
(95, 0),
(92, 0),
(83, 0),
(79, 0),
(78, 0),
(74, 3),
(63, 1),
(59, 3),
(57, 3),
(109, 0),
(108, 0),
(100, 5),
(93, 1),
(95, 0),
(92, 0),
(77, 1),
(79, 0),
(78, 0),
(117, 4),
(118, 5),
(114, 5),
(106, 1),
(109, 0),
(108, 0),
(90, 1),
(93, 1),
(95, 0),
// face 16
(90, 0),
(77, 0),
(63, 0),
(80, 0),
(68, 0),
(56, 3),
(72, 1),
(60, 3),
(46, 3),
(106, 0),
(93, 0),
(79, 5),
(99, 1),
(90, 0),
(77, 0),
(88, 1),
(80, 0),
(68, 0),
(117, 3),
(109, 5),
(95, 5),
(113, 1),
(106, 0),
(93, 0),
(105, 1),
(99, 1),
(90, 0),
// face 17
(105, 0),
(88, 0),
(72, 0),
(103, 0),
(91, 0),
(73, 3),
(97, 1),
(89, 3),
(71, 3),
(113, 0),
(99, 0),
(80, 5),
(116, 1),
(105, 0),
(88, 0),
(111, 1),
(103, 0),
(91, 0),
(117, 2),
(106, 5),
(90, 5),
(121, 1),
(113, 0),
(99, 0),
(119, 1),
(116, 1),
(105, 0),
// face 18
(119, 0),
(111, 0),
(97, 0),
(115, 0),
(110, 0),
(98, 3),
(107, 1),
(104, 3),
(96, 3),
(121, 0),
(116, 0),
(103, 5),
(120, 1),
(119, 0),
(111, 0),
(112, 1),
(115, 0),
(110, 0),
(117, 1),
(113, 5),
(105, 5),
(118, 1),
(121, 0),
(116, 0),
(114, 1),
(120, 1),
(119, 0),
// face 19
(114, 0),
(112, 0),
(107, 0),
(100, 0),
(102, 0),
(101, 3),
(83, 1),
(87, 3),
(85, 3),
(118, 0),
(120, 0),
(115, 5),
(108, 1),
(114, 0),
(112, 0),
(92, 1),
(100, 0),
(102, 0),
(117, 0),
(121, 5),
(119, 5),
(109, 1),
(118, 0),
(120, 0),
(95, 1),
(108, 1),
(114, 0),
]
///|
fn face_ijk_to_base_cell(face_ijk : FaceIJK) -> (Int, Int) {
let coordinate = face_ijk.1
let index = face_ijk.0 * 27 +
coordinate.0 * 9 +
coordinate.1 * 3 +
coordinate.2
face_ijk_base_cells[index]
}
///|
fn base_cell_is_cw_offset(base_cell : Int, face : Int) -> Bool {
match base_cell {
14 => face == 2 || face == 6
24 => face == 1 || face == 5
38 => face == 3 || face == 7
49 => face == 0 || face == 9
58 => face == 4 || face == 8
63 => face == 11 || face == 15
72 => face == 12 || face == 16
83 => face == 10 || face == 19
97 => face == 13 || face == 17
107 => face == 14 || face == 18
_ => false
}
}
///|
fn rotate_digit_60ccw(digit : Int) -> Int {
match digit {
1 => 5
5 => 4
4 => 6
6 => 2
2 => 3
3 => 1
_ => digit
}
}
///|
fn rotate_index_60ccw(value : UInt64) -> UInt64 {
let mut rotated = value
let mut current = 1
while current <= resolution_of(value) {
rotated = set_digit(
rotated,
current,
rotate_digit_60ccw(digit_of(rotated, current)),
)
current += 1
}
rotated
}
///|
fn rotate_pentagon_index_60ccw(value : UInt64) -> UInt64 {
let mut rotated = value
let mut found_nonzero = false
let mut current = 1
while current <= resolution_of(value) {
rotated = set_digit(
rotated,
current,
rotate_digit_60ccw(digit_of(rotated, current)),
)
if !found_nonzero && digit_of(rotated, current) != 0 {
found_nonzero = true
if leading_nonzero_digit(rotated) == 1 {
rotated = rotate_index_60ccw(rotated)
}
}
current += 1
}
rotated
}
///|
fn coord_up_ap7(value : CoordIJK) -> CoordIJK {
let i = value.0 - value.2
let j = value.1 - value.2
coord_normalize(
CoordIJK(
((3 * i - j).to_double() / 7.0).round().to_int(),
((i + 2 * j).to_double() / 7.0).round().to_int(),
0,
),
)
}
///|
fn unit_ijk_to_digit(value : CoordIJK) -> Int {
let normalized = coord_normalize(value)
match normalized {
CoordIJK(0, 0, 0) => 0
CoordIJK(0, 0, 1) => 1
CoordIJK(0, 1, 0) => 2
CoordIJK(0, 1, 1) => 3
CoordIJK(1, 0, 0) => 4
CoordIJK(1, 0, 1) => 5
CoordIJK(1, 1, 0) => 6
_ => 7
}
}
///|
fn set_base_cell(value : UInt64, base_cell : Int) -> UInt64 {
(value & UInt64::lnot(127UL << 45)) | (base_cell.to_uint64() << 45)
}
///|
fn face_ijk_to_h3(
face_ijk : FaceIJK,
resolution : Int,
) -> Result[UInt64, H3Error] {
let mut value = 35184372088831UL
value = value | (1UL << 59)
value = with_resolution(value, resolution)
if resolution == 0 {
let coordinate = face_ijk.1
if coordinate.0 > 2 || coordinate.1 > 2 || coordinate.2 > 2 {
return Err(InvalidCoordinate)
}
let (base_cell, _) = face_ijk_to_base_cell(face_ijk)
value = set_base_cell(value, base_cell)
return Ok(value)
}
let mut coordinate = face_ijk.1
let mut current = resolution - 1
while current >= 0 {
let last = coordinate
let last_center = if is_resolution_class_iii(current + 1) {
coordinate = coord_up_ap7(coordinate)
coord_down_ap7(coordinate)
} else {
coordinate = coord_up_ap7r(coordinate)
coord_down_ap7r(coordinate)
}
let difference = coord_normalize(coord_sub(last, last_center))
let digit = unit_ijk_to_digit(difference)
if digit == 7 {
return Err(InvalidCoordinate)
}
value = set_digit(value, current + 1, digit)
current -= 1
}
if coordinate.0 > 2 || coordinate.1 > 2 || coordinate.2 > 2 {
return Err(InvalidCoordinate)
}
let (base_cell, rotations) = face_ijk_to_base_cell(
FaceIJK(face_ijk.0, coordinate),
)
value = set_base_cell(value, base_cell)
if is_pentagon_base_cell(base_cell) {
if leading_nonzero_digit(value) == 1 {
value = if base_cell_is_cw_offset(base_cell, face_ijk.0) {
rotate_index_60cw(value)
} else {
rotate_index_60ccw(value)
}
}
let mut count = 0
while count < rotations {
value = rotate_pentagon_index_60ccw(value)
count += 1
}
} else {
let mut count = 0
while count < rotations {
value = rotate_index_60ccw(value)
count += 1
}
}
Ok(value)
}
///|
fn vec3_to_closest_face(point : Vec3) -> (Int, Double) {
let mut face = 0
let mut squared_distance = 5.0
let mut current = 0
while current < face_center_points.length() {
let difference = vec3_linear_combination(
1.0,
face_center_points[current],
-1.0,
point,
)
let candidate = vec3_dot(difference, difference)
if candidate < squared_distance {
face = current
squared_distance = candidate
}
current += 1
}
(face, squared_distance)
}
///|
fn vec3_azimuth_rads(origin : Vec3, point : Vec3) -> Double {
let (north, east) = tangent_basis(origin)
let projected = vec3_normalize(
vec3_linear_combination(1.0, point, -vec3_dot(point, origin), origin),
)
@math.atan2(vec3_dot(projected, east), vec3_dot(projected, north))
}
///|
fn vec3_to_hex_2d(point : Vec3, resolution : Int) -> (Int, Double, Double) {
let (face, squared_distance) = vec3_to_closest_face(point)
let distance = @math.acos(1.0 - squared_distance * 0.5)
if distance < EPSILON {
return (face, 0.0, 0.0)
}
let mut theta = positive_angle(
face_axes_azimuth[face] -
positive_angle(vec3_azimuth_rads(face_center_points[face], point)),
)
if is_resolution_class_iii(resolution) {
theta = positive_angle(theta - AP7_ROT_RADS)
}
let mut radius = @math.tan(distance) * INVERSE_RES0_U_GNOMONIC
let mut current = 0
while current < resolution {
radius *= SQRT7
current += 1
}
(face, radius * @math.cos(theta), radius * @math.sin(theta))
}
///|
fn hex_2d_to_coord_ijk(x : Double, y : Double) -> CoordIJK {
let a1 = x.abs()
let a2 = y.abs()
let x2 = a2 * INVERSE_SIN_60
let x1 = a1 + x2 / 2.0
let m1 = x1.to_int()
let m2 = x2.to_int()
let r1 = x1 - m1.to_double()
let r2 = x2 - m2.to_double()
let mut i = 0
let mut j = 0
if r1 < 0.5 {
if r1 < 1.0 / 3.0 {
i = m1
j = if r2 < (1.0 + r1) / 2.0 { m2 } else { m2 + 1 }
} else {
j = if r2 < 1.0 - r1 { m2 } else { m2 + 1 }
i = if 1.0 - r1 <= r2 && r2 < 2.0 * r1 { m1 + 1 } else { m1 }
}
} else if r1 < 2.0 / 3.0 {
j = if r2 < 1.0 - r1 { m2 } else { m2 + 1 }
i = if 2.0 * r1 - 1.0 < r2 && r2 < 1.0 - r1 { m1 } else { m1 + 1 }
} else if r2 < r1 / 2.0 {
i = m1 + 1
j = m2
} else {
i = m1 + 1
j = m2 + 1
}
if x < 0.0 {
if j % 2 == 0 {
let axis_i = j / 2
let difference = i - axis_i
i -= 2 * difference
} else {
let axis_i = (j + 1) / 2
let difference = i - axis_i
i -= 2 * difference + 1
}
}
if y < 0.0 {
i -= (2 * j + 1) / 2
j = -j
}
coord_normalize(CoordIJK(i, j, 0))
}
///|
fn lat_lng_to_vec3(latitude : Double, longitude : Double) -> Vec3 {
let latitude_radians = latitude / DEGREES_PER_RADIAN
let longitude_radians = longitude / DEGREES_PER_RADIAN
let ring_radius = @math.cos(latitude_radians)
Vec3(
@math.cos(longitude_radians) * ring_radius,
@math.sin(longitude_radians) * ring_radius,
@math.sin(latitude_radians),
)
}
///|
/// Encodes decimal latitude and longitude as the containing H3 cell.
pub fn H3Index::from_lat_lng(
latitude : Double,
longitude : Double,
resolution : Int,
) -> Result[H3Index, H3Error] {
if resolution < 0 || resolution > MAX_RESOLUTION {
return Err(InvalidResolution(resolution))
}
if latitude.is_nan() ||
longitude.is_nan() ||
latitude < -90.0 ||
latitude > 90.0 ||
longitude < -180.0 ||
longitude > 180.0 {
return Err(InvalidCoordinate)
}
let (face, x, y) = vec3_to_hex_2d(
lat_lng_to_vec3(latitude, longitude),
resolution,
)
let index = hex_2d_to_coord_ijk(x, y)
let value = match face_ijk_to_h3(FaceIJK(face, index), resolution) {
Ok(value) => value
Err(error) => return Err(error)
}
H3Index::from_u64(value)
}