///|
/// A validated H3 cell index.
///
/// The bit layout follows the Uber H3 indexing specification. Values of this
/// type always represent a valid H3 hexagonal or pentagonal cell.
pub struct H3Index(UInt64) derive(Eq, Hash)
///|
/// Errors produced while parsing or validating an H3 cell index.
pub enum H3Error {
EmptyInput
TooLong(Int)
InvalidCharacter(Char)
InvalidIndex
InvalidResolution(Int)
ResolutionMismatch(Int, Int)
InvalidCoordinate
InvalidGridDiskRadius(Int)
InvalidDirection(Int)
UnavailableNeighbor(Int)
GridDistanceOutOfRange
} derive(Eq, Debug)
///|
/// The highest supported H3 resolution.
pub const MAX_RESOLUTION : Int = 15
///|
/// The number of H3 base cells, including the twelve pentagons.
pub const NUM_BASE_CELLS : Int = 122
///|
fn hex_value(ch : Char) -> UInt64? {
if ch >= '0' && ch <= '9' {
Some((ch.to_int() - '0'.to_int()).to_uint64())
} else if ch >= 'a' && ch <= 'f' {
Some((ch.to_int() - 'a'.to_int() + 10).to_uint64())
} else if ch >= 'A' && ch <= 'F' {
Some((ch.to_int() - 'A'.to_int() + 10).to_uint64())
} else {
None
}
}
///|
fn is_pentagon_base_cell(base_cell : Int) -> Bool {
match base_cell {
4 | 14 | 24 | 38 | 49 | 58 | 63 | 72 | 83 | 97 | 107 | 117 => true
_ => false
}
}
///|
fn resolution_of(value : UInt64) -> Int {
((value >> 52) & 15UL).to_int()
}
///|
fn base_cell_of(value : UInt64) -> Int {
((value >> 45) & 127UL).to_int()
}
///|
fn digit_of(value : UInt64, resolution : Int) -> Int {
((value >> ((MAX_RESOLUTION - resolution) * 3)) & 7UL).to_int()
}
///|
fn with_resolution(value : UInt64, resolution : Int) -> UInt64 {
(value & UInt64::lnot(15UL << 52)) | (resolution.to_uint64() << 52)
}
///|
fn parent_tail_mask(resolution : Int) -> UInt64 {
(1UL << ((MAX_RESOLUTION - resolution) * 3)) - 1UL
}
///|
fn child_digit_mask(current_resolution : Int, child_resolution : Int) -> UInt64 {
let digits_below_current = (
1UL << ((MAX_RESOLUTION - current_resolution) * 3)
) -
1UL
digits_below_current & UInt64::lnot(parent_tail_mask(child_resolution))
}
///|
/// Returns whether `value` is a valid H3 cell index.
///
/// This validates the top byte, base cell range, active digits, unused digits,
/// and the deleted subsequence rule for pentagonal base cells.
pub fn is_valid_index(value : UInt64) -> Bool {
if value >> 56 != 8UL {
return false
}
let resolution = resolution_of(value)
let base_cell = base_cell_of(value)
if base_cell >= NUM_BASE_CELLS {
return false
}
let mut current = 1
while current <= resolution {
if digit_of(value, current) == 7 {
return false
}
current += 1
}
while current <= MAX_RESOLUTION {
if digit_of(value, current) != 7 {
return false
}
current += 1
}
if is_pentagon_base_cell(base_cell) {
current = 1
while current <= MAX_RESOLUTION {
let digit = digit_of(value, current)
if digit != 0 {
return digit != 1
}
current += 1
}
}
true
}
///|
/// Creates an H3 index from its raw 64-bit representation.
pub fn H3Index::from_u64(value : UInt64) -> Result[H3Index, H3Error] {
if is_valid_index(value) {
Ok(H3Index(value))
} else {
Err(InvalidIndex)
}
}
///|
/// Parses an H3 cell index from one to sixteen hexadecimal digits.
///
/// Parsing is case-insensitive and accepts leading zeroes. The resulting
/// value is validated before it is returned.
pub fn H3Index::parse(text : String) -> Result[H3Index, H3Error] {
let chars = text.to_array()
let length = chars.length()
if length == 0 {
return Err(EmptyInput)
}
if length > 16 {
return Err(TooLong(length))
}
let mut value = 0UL
for ch in chars {
match hex_value(ch) {
Some(digit) => value = (value << 4) | digit
None => return Err(InvalidCharacter(ch))
}
}
H3Index::from_u64(value)
}
///|
/// Returns the raw 64-bit H3 representation.
pub fn H3Index::raw(self : H3Index) -> UInt64 {
self.0
}
///|
/// Returns the canonical lower-case hexadecimal representation.
pub fn H3Index::to_string(self : H3Index) -> String {
self.0.to_string(radix=16)
}
///|
/// Returns the resolution of this cell, from 0 through 15.
pub fn H3Index::resolution(self : H3Index) -> Int {
resolution_of(self.0)
}
///|
/// Returns the base cell number of this cell, from 0 through 121.
pub fn H3Index::base_cell(self : H3Index) -> Int {
base_cell_of(self.0)
}
///|
/// Returns the index digit for `resolution`, when that resolution is present.
///
/// Resolution numbering starts at 1 and ends at `self.resolution()`.
pub fn H3Index::digit(self : H3Index, resolution : Int) -> Int? {
if resolution < 1 || resolution > self.resolution() {
None
} else {
Some(digit_of(self.0, resolution))
}
}
///|
/// Returns the ancestor of this cell at `resolution`.
///
/// Requesting the current resolution returns the same cell.
pub fn H3Index::parent(
self : H3Index,
resolution : Int,
) -> Result[H3Index, H3Error] {
let current = self.resolution()
if resolution < 0 || resolution > MAX_RESOLUTION {
return Err(InvalidResolution(resolution))
}
if resolution > current {
return Err(ResolutionMismatch(resolution, current))
}
if resolution == current {
return Ok(self)
}
let mask = parent_tail_mask(resolution)
let value = (with_resolution(self.0, resolution) & UInt64::lnot(mask)) | mask
H3Index::from_u64(value)
}
///|
/// Returns the center descendant of this cell at `resolution`.
///
/// Requesting the current resolution returns the same cell.
pub fn H3Index::center_child(
self : H3Index,
resolution : Int,
) -> Result[H3Index, H3Error] {
let current = self.resolution()
if resolution < 0 || resolution > MAX_RESOLUTION {
return Err(InvalidResolution(resolution))
}
if resolution < current {
return Err(ResolutionMismatch(resolution, current))
}
if resolution == current {
return Ok(self)
}
let mask = child_digit_mask(current, resolution)
let value = with_resolution(
(self.0 & UInt64::lnot(mask)) | parent_tail_mask(resolution),
resolution,
)
H3Index::from_u64(value)
}
///|
/// Returns whether this cell is an ancestor of `other`.
///
/// A cell is considered an ancestor of itself.
pub fn H3Index::is_ancestor_of(self : H3Index, other : H3Index) -> Bool {
if self.resolution() > other.resolution() {
return false
}
match other.parent(self.resolution()) {
Ok(parent) => parent == self
Err(_) => false
}
}
///|
/// Returns whether this cell is a descendant of `other`.
///
/// A cell is considered a descendant of itself.
pub fn H3Index::is_descendant_of(self : H3Index, other : H3Index) -> Bool {
other.is_ancestor_of(self)
}
///|
/// Returns whether this cell is one of the twelve pentagons at its resolution.
pub fn H3Index::is_pentagon(self : H3Index) -> Bool {
if !is_pentagon_base_cell(self.base_cell()) {
return false
}
let mut current = 1
while current <= self.resolution() {
if digit_of(self.0, current) != 0 {
return false
}
current += 1
}
true
}