From 7266b4f0046d598bfd5896a7fc32f7276ad6d22d Mon Sep 17 00:00:00 2001 From: Gilles Colling Date: Wed, 22 Jul 2026 13:12:31 +0200 Subject: [PATCH 1/2] fix R CMD check for bijective Z7 indices --- R/hexify_index.R | 14 ++--- man/hexify_z7_canonical.Rd | 12 ++-- src/index_z7.cpp | 4 +- tests/testthat/fixtures/test_cache.rds | Bin 1077 -> 1125 bytes tests/testthat/generate_cache.R | 2 +- tests/testthat/test-index-z7.R | 84 ++++++------------------- 6 files changed, 34 insertions(+), 82 deletions(-) diff --git a/R/hexify_index.R b/R/hexify_index.R index 3e247b1..e8db79a 100644 --- a/R/hexify_index.R +++ b/R/hexify_index.R @@ -292,22 +292,20 @@ hexify_cell_id_to_quad_ij <- function(cell_id, resolution, aperture) { #' Get canonical form of Z7 index #' -#' For Z7 indices that form cycles during decode/encode, returns the -#' lexicographically smallest index in the cycle. Provides stable -#' unique identifiers for aperture 7 grids. +#' Returns the stable form of a Z7 index after decode/encode. Z7 indices are +#' bijective, so valid indices are already canonical. #' #' @param index Z7 index string -#' @param max_iterations Maximum iterations for cycle detection (default 128) +#' @param max_iterations Maximum decode/encode iterations (default 128) #' -#' @return Canonical form (lexicographically smallest in cycle) +#' @return Stable canonical form of the index #' #' @family hierarchical index #' @keywords internal #' @export #' @examples -#' # These all return the same canonical form -#' hexify_z7_canonical("110001") -#' hexify_z7_canonical("110002") +#' # Valid Z7 indices are stable +#' hexify_z7_canonical("110001") hexify_z7_canonical <- function(index, max_iterations = 128L) { cpp_z7_canonical_form(as.character(index), as.integer(max_iterations)) } diff --git a/man/hexify_z7_canonical.Rd b/man/hexify_z7_canonical.Rd index aaf65df..58e3f40 100644 --- a/man/hexify_z7_canonical.Rd +++ b/man/hexify_z7_canonical.Rd @@ -9,20 +9,18 @@ hexify_z7_canonical(index, max_iterations = 128L) \arguments{ \item{index}{Z7 index string} -\item{max_iterations}{Maximum iterations for cycle detection (default 128)} +\item{max_iterations}{Maximum decode/encode iterations (default 128)} } \value{ -Canonical form (lexicographically smallest in cycle) +Stable canonical form of the index } \description{ -For Z7 indices that form cycles during decode/encode, returns the -lexicographically smallest index in the cycle. Provides stable -unique identifiers for aperture 7 grids. +Returns the stable form of a Z7 index after decode/encode. Z7 indices are +bijective, so valid indices are already canonical. } \examples{ -# These all return the same canonical form +# Valid Z7 indices are stable hexify_z7_canonical("110001") -hexify_z7_canonical("110002") } \seealso{ Other hierarchical index: diff --git a/src/index_z7.cpp b/src/index_z7.cpp index 22a6feb..78c7626 100644 --- a/src/index_z7.cpp +++ b/src/index_z7.cpp @@ -492,8 +492,8 @@ std::string canonical_form(const std::string& z7_index, int max_iterations) { long long i, j; int res = current.length() - 2; - decode(current, res, quadNum, i, j); - std::string next = encode(quadNum, i, j, res); + decode_bijective(current, res, quadNum, i, j); + std::string next = encode_bijective(quadNum, i, j, res); // Check for fixed point if (next == current) { diff --git a/tests/testthat/fixtures/test_cache.rds b/tests/testthat/fixtures/test_cache.rds index 7592361cc779cf5e5bd6c465c51bf7cdd344f5d3..b304033d0fb5995a765bdc84cd4e7943b538fc78 100644 GIT binary patch literal 1125 zcmV-r1e*IFiwFP!0000028-ZgU|?WoU||E2tUx9MYiNj@t_1@FlQ59O38g_Yd?4E4 zFJA#jmBZg-AHej#7YYzQg9DiU^N#@v!0Pv(j*m_eQOC zU>2R1A*9HBfM-Sc;a_SlaP!?-Yp-n26m#(Sb60a(#zY4Xl}mgatt}27)w%`mjSo7w zI6eJrzWzHPIs z;Lsodp{GY6rz+x6Rq$pw<8qV?SW$@#-sQP(ED0yQS8Jw5YQV9*?{V(r>;v zpnqN=sZsU|ng^I+Q3#5PVkn0h5k<_Pn8D_}1!WLtE!YR4cT}S|&;IA^L*@BU0Y>+jliCMX?Ej$dP`domA&|P{_?KVpzvb`u+HsG=fhBv6sdYx`{@+*3W6#%U zg3PN?7CH|yM<;RPeg{UrXvqTS9S7L^Z_ne?s{omI;@{&9VEebd=mOhw{PP8{Icdde zAa!%E-%5wM)Bdw}{k^V47yHMXm>OC=ZrNXte0w2GqaMx&%0Ikjd=|v7=bx5uyXZn> zEZ4b<`$6hFCe4TPFM;G6Kgv$nyXZncpM}|${Sf}d{SbXcFmZc`zC|GMx`Rt5?+2QH z|6&CbhZb4CgR(x81QF&q!y!rxVIZ#zq rl3E1I==_;^DXA6lRmSnfiMa(iupG$_3WE5|ERf3qq!ZNmTL=IECQ2py literal 1077 zcmV-51j_p#iwFP!0000028-ZgU|?WoU}0rqU}gm}8CXL@+;lA%7?^~C98M?=lHmi< z4uAOyK&l-69{T{M|GiLv@EIJy^q+qWPyklH|8$Jp2lIug4$9k$1OpPEI~Zio+q^ew ztpl^@ybK{l<^wz{!VmvabAg-h)>?aId#0F!$Dg~J(=sMHc&J?B<7jPh@Tk@;cyD~r z!Nuw6XY=*nfogeym=TCU9uNStSwQY(U^Hf6U|~WMV9iU+O)ZA1VucE_C#UA*#Al{J zxjbNQa%x_DPJSMySYioOjH@WMI6tSfBonTRBe5X0sHC(g70CVv4K5Y}&I9|E(V^>K z;_^p_%He$bsmua8;%*S!g;2RzE7_Mlenf_`>~>o1Nu_a zb+yj$*=u-3zhjlDziOH~ZC6Jk%Uz`|UQk0pP zm!6XvUx7os65Na-{rjvc&2Yg# z_J7>0_FXJoc7V}6=A`z475hJ^JCrWJbO@v_IsWBW`)~RCy>{H=aA3)vV``m|y8rhT z^Vst>njrIPl!eZN%+X2QxZi=1FIuv|dB*|v{@e5T^eRB+o%r{71K9p;FS@|?9RGX) zY))FS8c5yT>$lQj?zI2xU4O4D(Z&ApCZ>i~k6ZTFBi~*K)2N5@f$|Tp8J`95>-neU z+b+5g8OwF<;(m}ik4f{P{7WGD#*eZS_Aa{6&u3w_Wj};JaX&;~5lq}3qHhsMyzbzV z$@_uk-@jPF1fn1G%L;^ks?T5Id4lSOTaHRy)qAxiou^3kL!34Qd5=#>G(u#nk zBARl}qWp3_SZN6=z?eW8|NsC0Zy+&?VhpQ+fq{`BG`g9g8kiX%r5+6PLB$|BL;y;g zpd?LZhlmD= 0L || result_neg$j >= 0L, - info = "Negative coords should be handled") + expect_equal(c(result_neg$i, result_neg$j), c(-1, -1), + info = "Negative coords should round-trip") }) test_that("Z7: Resolution progression works correctly", { @@ -217,52 +204,21 @@ test_that("Z7: Resolution progression works correctly", { expect_equal(nchar(idx_r3), 2L + 3L) # Face + 3 digits }) -test_that("Z7: Known problem indices behave as expected", { - # Test specific indices that we know don't round-trip - # These are not bugs but expected behavior for aperture 7 - - # Face 1 with digit 2 should re-encode to digit 6 after remapping - result <- hexify_index_to_cell("012", 7L, "z7") - re_encoded <- hexify_cell_to_index(result$face, result$i, result$j, - result$resolution, 7L, "z7") - expect_equal(re_encoded, "016", - info = "012 should re-encode to 016 (DGGRID behavior)") - - # The 110001 cycle - each re-encodes to the next in cycle - cycle_test <- list( - list(idx = "110001", expected = "110002"), - list(idx = "110002", expected = "110004"), - list(idx = "110004", expected = "110006"), - list(idx = "110006", expected = "110001") # Completes cycle - ) - - for (tc in cycle_test) { - result <- hexify_index_to_cell(tc$idx, 7L, "z7") +test_that("Z7: Former DGGRID problem indices are stable", { + problem_indices <- c("012", "110001", "110002", "110004", "110006") + + for (idx in problem_indices) { + result <- hexify_index_to_cell(idx, 7L, "z7") re_encoded <- hexify_cell_to_index(result$face, result$i, result$j, result$resolution, 7L, "z7") - expect_equal(re_encoded, tc$expected, - info = sprintf("%s should re-encode to %s (part of cycle)", - tc$idx, tc$expected)) + expect_equal(re_encoded, idx, + info = sprintf("%s should round-trip", idx)) } }) test_that("Z7: Canonical forms provide stability", { - # Test canonical forms - provides stable unique identifiers for cells - - # The 110001 cycle - all should have same canonical form - cycle_indices <- c("110001", "110002", "110004", "110006") - canonicals <- vapply(cycle_indices, hexify_z7_canonical, character(1)) - expect_true(all(canonicals == "110001"), - info = "All cycle members should canonicalize to 110001") - - # Test other known transformations - expect_equal(hexify_z7_canonical("012"), "016", - info = "012 should canonicalize to 016") - expect_equal(hexify_z7_canonical("0955555"), "0911111", - info = "0955555 should canonicalize to 0911111") - - # Canonical forms should be stable - for (idx in c("110001", "016", "0911111")) { + for (idx in c("012", "110001", "110002", "110004", "110006", "0955555")) { + expect_equal(hexify_z7_canonical(idx), idx) canonical <- hexify_z7_canonical(idx) result <- hexify_index_to_cell(canonical, 7L, "z7") re_encoded <- hexify_cell_to_index(result$face, result$i, result$j, From b156c61f6a31e927167c702463eb013adc6bc307 Mon Sep 17 00:00:00 2001 From: Gilles Colling Date: Wed, 22 Jul 2026 13:19:27 +0200 Subject: [PATCH 2/2] release v0.7.4 --- DESCRIPTION | 2 +- NEWS.md | 12 ++++-- R/grid_helpers.R | 2 +- R/hexify_index.R | 77 +++++++++++++++++++++++++---------- man/hexify_cell_to_index.Rd | 19 +++++++-- man/hexify_index_to_cell.Rd | 11 ++++- man/hexify_index_to_lonlat.Rd | 6 ++- man/hexify_lonlat_to_index.Rd | 8 +++- man/hexify_z7_canonical.Rd | 21 +++++++--- vignettes/theory.Rmd | 27 +++++++++++- 10 files changed, 142 insertions(+), 43 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index bdff7e5..642efcf 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: hexify Title: Equal-Area Hex Grids on the 'Snyder' 'ISEA' 'Icosahedron' -Version: 0.7.3.9000 +Version: 0.7.4 Authors@R: person("Gilles", "Colling", , "gilles.colling051@gmail.com", role = c("aut", "cre", "cph"), comment = c(ORCID = "0000-0003-3070-6066")) diff --git a/NEWS.md b/NEWS.md index eafdca2..9bf942c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,12 @@ -# hexify 0.7.4 (development version) - -## New features +# hexify 0.7.4 + +## Documentation + +* Documented the bijective aperture-7 Z7 format, exact round-trip guarantee, + index structure, and the limited pentagon-region difference from DGGRID's + non-injective raw Z7 encoding. + +## New features * Hierarchical navigation now works for mixed aperture `"4/3"` (ISEA43H) grids: `get_parent()`, `get_children()`, and `cell_to_index()` no longer error on diff --git a/R/grid_helpers.R b/R/grid_helpers.R index c0b7acf..eb6612d 100644 --- a/R/grid_helpers.R +++ b/R/grid_helpers.R @@ -8,7 +8,7 @@ #' Normalize antimeridian-crossing polygon coordinates #' #' Shifts longitudes of a ring's coordinate matrix so an antimeridian-crossing -#' polygon becomes contiguous instead of spanning nearly the full [-180, 180] +#' polygon becomes contiguous instead of spanning nearly the full -180 to 180 #' range. Downstream `sf::st_wrap_dateline()` then splits it correctly for #' flat-map rendering. #' diff --git a/R/hexify_index.R b/R/hexify_index.R index e8db79a..a9a3b4d 100644 --- a/R/hexify_index.R +++ b/R/hexify_index.R @@ -15,8 +15,8 @@ #' Convert cell coordinates to index string #' -#' Converts DGGRID cell coordinates (face, i, j) to a hierarchical index string. -#' The index type is automatically selected based on aperture unless specified. +#' Converts cell coordinates (`face`, `i`, `j`) to a hierarchical index string. +#' The index type is selected from `aperture` when `index_type = "auto"`. #' #' @param face Face/quad number (0-19) #' @param i I coordinate @@ -25,19 +25,30 @@ #' @param aperture Aperture (3, 4, or 7) #' @param index_type Index encoding: "auto" (default), "z3", "z7", or "zorder" #' -#' @return Index string (e.g., "051223") +#' @return A character vector of index strings. #' #' @details #' Default index types by aperture: #' - Aperture 3: Z3 (optimized digit selection) #' - Aperture 4: Z-order (Morton curve) -#' - Aperture 7: Z7 (hierarchical with Class III handling) +#' - Aperture 7: Z7 (one base-7 child digit per resolution) +#' +#' A Z7 index has the form `BBd1...dr`, where `BB` is the two-digit base +#' cell (00--11), `r` is the resolution, and every child digit is in 0--6. +#' hexify's Z7 encoding is bijective: decoding and re-encoding a valid index +#' returns the same string. It follows DGGRID's Z7 layout for ordinary cells, +#' but retains distinct indices in pentagon regions where DGGRID's encoder can +#' map different cells to the same string. #' #' @family hierarchical index #' @keywords internal #' @export -#' @examples -#' idx <- hexify_cell_to_index(5, 10, 15, resolution = 3, aperture = 3) +#' @examples +#' idx <- hexify_cell_to_index(5, 10, 15, resolution = 3, aperture = 3) +#' +#' # Aperture-7 indices contain a two-digit base cell and one digit per level +#' z7 <- hexify_cell_to_index(5, 1, 1, resolution = 2, aperture = 7) +#' nchar(z7) == 4L hexify_cell_to_index <- function(face, i, j, resolution, aperture = 3L, index_type = c("auto", "z3", "z7", "zorder")) { index_type <- match.arg(index_type) @@ -51,19 +62,26 @@ hexify_cell_to_index <- function(face, i, j, resolution, aperture = 3L, #' Convert index string to cell coordinates #' -#' Decodes a hierarchical index string back to cell coordinates. +#' Decodes a hierarchical index string back to its cell coordinates and +#' resolution. For Z7, valid indices round-trip exactly through +#' [hexify_cell_to_index()]. #' #' @param index Index string #' @param aperture Aperture (3, 4, or 7) #' @param index_type Index encoding used. Default "auto" infers from aperture. #' -#' @return List with face, i, j, and resolution +#' @return A list with `face`, `i`, `j`, and `resolution`. #' #' @family hierarchical index #' @keywords internal #' @export -#' @examples -#' cell <- hexify_index_to_cell("0012012", aperture = 3) +#' @examples +#' cell <- hexify_index_to_cell("0012012", aperture = 3) +#' +#' z7_cell <- hexify_index_to_cell("110001", aperture = 7) +#' hexify_cell_to_index(z7_cell$face, z7_cell$i, z7_cell$j, +#' z7_cell$resolution, aperture = 7 +#' ) hexify_index_to_cell <- function(index, aperture = 3L, index_type = c("auto", "z3", "z7", "zorder")) { index_type <- match.arg(index_type) @@ -73,7 +91,8 @@ hexify_index_to_cell <- function(index, aperture = 3L, #' Convert longitude/latitude to index string #' -#' Main entry point for geocoding points to grid cells. +#' Projects geographic coordinates to grid cells and returns their hierarchical +#' index strings. Inputs are vectorized over `lon` and `lat`. #' #' @param lon Longitude in degrees #' @param lat Latitude in degrees @@ -81,13 +100,16 @@ hexify_index_to_cell <- function(index, aperture = 3L, #' @param aperture Aperture (3, 4, or 7) #' @param index_type Index encoding: "auto" (default), "z3", "z7", or "zorder" #' -#' @return Index string +#' @return A character vector of index strings. #' #' @family hierarchical index #' @keywords internal #' @export -#' @examples -#' idx <- hexify_lonlat_to_index(16.37, 48.21, resolution = 5, aperture = 3) +#' @examples +#' idx <- hexify_lonlat_to_index(16.37, 48.21, resolution = 5, aperture = 3) +#' idx7 <- hexify_lonlat_to_index(16.37, 48.21, +#' resolution = 4, aperture = 7 +#' ) hexify_lonlat_to_index <- function(lon, lat, resolution, aperture = 3L, index_type = c("auto", "z3", "z7", "zorder")) { index_type <- match.arg(index_type) @@ -99,13 +121,15 @@ hexify_lonlat_to_index <- function(lon, lat, resolution, aperture = 3L, #' Convert index string to longitude/latitude #' -#' Returns the cell center coordinates for a given index. +#' Returns the geographic coordinates of an indexed cell's center. This is a +#' cell-level inverse of [hexify_lonlat_to_index()]: the returned point is the +#' center, not necessarily the original input point. #' #' @param index Index string #' @param aperture Aperture (3, 4, or 7) #' @param index_type Index encoding. Default "auto" infers from aperture. #' -#' @return Named numeric vector with lon and lat in degrees +#' @return A named numeric vector with `lon` and `lat` in degrees. #' #' @family hierarchical index #' @keywords internal @@ -292,13 +316,16 @@ hexify_cell_id_to_quad_ij <- function(cell_id, resolution, aperture) { #' Get canonical form of Z7 index #' -#' Returns the stable form of a Z7 index after decode/encode. Z7 indices are -#' bijective, so valid indices are already canonical. +#' Decodes and re-encodes a Z7 index until it reaches a stable form. Current Z7 +#' indices are bijective, so every valid index is already canonical and this +#' function normally returns its input unchanged. It remains available for +#' validating or normalizing indices created by older hexify versions. #' -#' @param index Z7 index string -#' @param max_iterations Maximum decode/encode iterations (default 128) +#' @param index A length-one Z7 index string. +#' @param max_iterations Maximum number of decode/encode iterations. This is a +#' safety bound for legacy indices; the default is 128. #' -#' @return Stable canonical form of the index +#' @return A length-one character string containing the stable index. #' #' @family hierarchical index #' @keywords internal @@ -306,6 +333,14 @@ hexify_cell_id_to_quad_ij <- function(cell_id, resolution, aperture) { #' @examples #' # Valid Z7 indices are stable #' hexify_z7_canonical("110001") +#' +#' cell <- hexify_index_to_cell("110001", aperture = 7) +#' identical( +#' hexify_cell_to_index(cell$face, cell$i, cell$j, +#' cell$resolution, aperture = 7 +#' ), +#' "110001" +#' ) hexify_z7_canonical <- function(index, max_iterations = 128L) { cpp_z7_canonical_form(as.character(index), as.integer(max_iterations)) } diff --git a/man/hexify_cell_to_index.Rd b/man/hexify_cell_to_index.Rd index 1066407..092d5fc 100644 --- a/man/hexify_cell_to_index.Rd +++ b/man/hexify_cell_to_index.Rd @@ -27,22 +27,33 @@ hexify_cell_to_index( \item{index_type}{Index encoding: "auto" (default), "z3", "z7", or "zorder"} } \value{ -Index string (e.g., "051223") +A character vector of index strings. } \description{ -Converts DGGRID cell coordinates (face, i, j) to a hierarchical index string. -The index type is automatically selected based on aperture unless specified. +Converts cell coordinates (\code{face}, \code{i}, \code{j}) to a hierarchical index string. +The index type is selected from \code{aperture} when \code{index_type = "auto"}. } \details{ Default index types by aperture: \itemize{ \item Aperture 3: Z3 (optimized digit selection) \item Aperture 4: Z-order (Morton curve) -\item Aperture 7: Z7 (hierarchical with Class III handling) +\item Aperture 7: Z7 (one base-7 child digit per resolution) } + +A Z7 index has the form \code{BBd1...dr}, where \code{BB} is the two-digit base +cell (00--11), \code{r} is the resolution, and every child digit is in 0--6. +hexify's Z7 encoding is bijective: decoding and re-encoding a valid index +returns the same string. It follows DGGRID's Z7 layout for ordinary cells, +but retains distinct indices in pentagon regions where DGGRID's encoder can +map different cells to the same string. } \examples{ idx <- hexify_cell_to_index(5, 10, 15, resolution = 3, aperture = 3) + +# Aperture-7 indices contain a two-digit base cell and one digit per level +z7 <- hexify_cell_to_index(5, 1, 1, resolution = 2, aperture = 7) +nchar(z7) == 4L } \seealso{ Other hierarchical index: diff --git a/man/hexify_index_to_cell.Rd b/man/hexify_index_to_cell.Rd index c591223..6f9edd1 100644 --- a/man/hexify_index_to_cell.Rd +++ b/man/hexify_index_to_cell.Rd @@ -18,13 +18,20 @@ hexify_index_to_cell( \item{index_type}{Index encoding used. Default "auto" infers from aperture.} } \value{ -List with face, i, j, and resolution +A list with \code{face}, \code{i}, \code{j}, and \code{resolution}. } \description{ -Decodes a hierarchical index string back to cell coordinates. +Decodes a hierarchical index string back to its cell coordinates and +resolution. For Z7, valid indices round-trip exactly through +\code{\link[=hexify_cell_to_index]{hexify_cell_to_index()}}. } \examples{ cell <- hexify_index_to_cell("0012012", aperture = 3) + +z7_cell <- hexify_index_to_cell("110001", aperture = 7) +hexify_cell_to_index(z7_cell$face, z7_cell$i, z7_cell$j, + z7_cell$resolution, aperture = 7 +) } \seealso{ Other hierarchical index: diff --git a/man/hexify_index_to_lonlat.Rd b/man/hexify_index_to_lonlat.Rd index 065499f..87bffdf 100644 --- a/man/hexify_index_to_lonlat.Rd +++ b/man/hexify_index_to_lonlat.Rd @@ -18,10 +18,12 @@ hexify_index_to_lonlat( \item{index_type}{Index encoding. Default "auto" infers from aperture.} } \value{ -Named numeric vector with lon and lat in degrees +A named numeric vector with \code{lon} and \code{lat} in degrees. } \description{ -Returns the cell center coordinates for a given index. +Returns the geographic coordinates of an indexed cell's center. This is a +cell-level inverse of \code{\link[=hexify_lonlat_to_index]{hexify_lonlat_to_index()}}: the returned point is the +center, not necessarily the original input point. } \examples{ coords <- hexify_index_to_lonlat("0012012", aperture = 3) diff --git a/man/hexify_lonlat_to_index.Rd b/man/hexify_lonlat_to_index.Rd index 297e967..4aea58a 100644 --- a/man/hexify_lonlat_to_index.Rd +++ b/man/hexify_lonlat_to_index.Rd @@ -24,13 +24,17 @@ hexify_lonlat_to_index( \item{index_type}{Index encoding: "auto" (default), "z3", "z7", or "zorder"} } \value{ -Index string +A character vector of index strings. } \description{ -Main entry point for geocoding points to grid cells. +Projects geographic coordinates to grid cells and returns their hierarchical +index strings. Inputs are vectorized over \code{lon} and \code{lat}. } \examples{ idx <- hexify_lonlat_to_index(16.37, 48.21, resolution = 5, aperture = 3) +idx7 <- hexify_lonlat_to_index(16.37, 48.21, + resolution = 4, aperture = 7 +) } \seealso{ Other hierarchical index: diff --git a/man/hexify_z7_canonical.Rd b/man/hexify_z7_canonical.Rd index 58e3f40..48a6c33 100644 --- a/man/hexify_z7_canonical.Rd +++ b/man/hexify_z7_canonical.Rd @@ -7,20 +7,31 @@ hexify_z7_canonical(index, max_iterations = 128L) } \arguments{ -\item{index}{Z7 index string} +\item{index}{A length-one Z7 index string.} -\item{max_iterations}{Maximum decode/encode iterations (default 128)} +\item{max_iterations}{Maximum number of decode/encode iterations. This is a +safety bound for legacy indices; the default is 128.} } \value{ -Stable canonical form of the index +A length-one character string containing the stable index. } \description{ -Returns the stable form of a Z7 index after decode/encode. Z7 indices are -bijective, so valid indices are already canonical. +Decodes and re-encodes a Z7 index until it reaches a stable form. Current Z7 +indices are bijective, so every valid index is already canonical and this +function normally returns its input unchanged. It remains available for +validating or normalizing indices created by older hexify versions. } \examples{ # Valid Z7 indices are stable hexify_z7_canonical("110001") + +cell <- hexify_index_to_cell("110001", aperture = 7) +identical( + hexify_cell_to_index(cell$face, cell$i, cell$j, + cell$resolution, aperture = 7 + ), + "110001" +) } \seealso{ Other hierarchical index: diff --git a/vignettes/theory.Rmd b/vignettes/theory.Rmd index b725c94..ec148cb 100644 --- a/vignettes/theory.Rmd +++ b/vignettes/theory.Rmd @@ -595,7 +595,18 @@ where **BB** is the base cell (00--11) and each digit $D_k \in \{0, 1, \ldots, 6 | 5 | IK_AXES | IK-axis direction | | 6 | IJ_AXES | IJ-axis direction | -Pentagon cells (at icosahedron vertices) have only 5 children instead of 7. Base cells 0--5 skip digit 2 (J_AXES); cells 6--11 skip digit 5 (IK_AXES). +The first two characters always identify the base cell, so the resolution is +`nchar(index) - 2`. Dropping the final digit gives the parent; appending digits +0--6 enumerates the seven positions in the next refinement level. + +hexify uses a **bijective Z7 variant**. DGGRID's base-cell reassignment and +pentagon digit-skip rules can make its encoder non-injective near pentagons: +distinct cells may receive the same string. hexify keeps the geographic quad +fixed in those regions, giving every cell a distinct index and guaranteeing +`index -> cell -> index` round-trips. Away from those pentagon-region cases, +the strings follow the DGGRID Z7 layout. This distinction matters when +exchanging raw Z7 strings with DGGRID; geographic coordinates and cell geometry +remain the safest interoperability layer. ```{r z7-example} # Z7 index encoding for aperture 7 @@ -612,6 +623,14 @@ parent_info <- hexify_index_to_cell(parent_idx, 7, "z7") cat(sprintf(" Parent index: %s (face %d, i=%d, j=%d)\n", parent_idx, parent_info$face, as.integer(parent_info$i), as.integer(parent_info$j))) + +# A valid Z7 index decodes and re-encodes without changing +decoded <- hexify_index_to_cell(idx, 7, "z7") +stopifnot(identical( + hexify_cell_to_index(decoded$face, decoded$i, decoded$j, + decoded$resolution, 7, "z7"), + idx +)) ``` #### Z3 Index (Aperture 3) @@ -662,7 +681,11 @@ cat(sprintf("Aperture 4: Cell %d -> Z-order index: %s\n", cell, idx)) | **Parent operation** | Drop last digit | Drop last pair | Drop last digit(s) | | **Index length** (res $r$) | $2 + r$ | $2 + 2\lceil r/2\rceil$ | $2 + r$ or $2 + 2r$ | -All three encodings are bijective: each $(quad, i, j)$ triple maps to exactly one index string, and vice versa. hexify stores indices as character strings to support arbitrary precision and avoid integer overflow at high resolutions. +All three hexify encodings are bijective: each valid $(quad, i, j)$ cell maps +to exactly one index string, and vice versa. hexify stores indices as character +strings to support arbitrary precision and avoid integer overflow at high +resolutions. As noted above, hexify's bijective Z7 strings intentionally differ +from DGGRID in the pentagon regions where DGGRID's raw Z7 encoding collides. ### SEQNUM: The Flat Cell ID