Skip to content

Core API Reference

Yoro Codec — Geographic addressing via 2D Hilbert curves.

Bijection between GPS coordinates and compact alphanumeric codes. Pure Python, zero external dependencies.

Theory: Paul Guindo, Altius Academy SNC.

encode(lat, lon, precision=12, domain='CI')

Encode GPS coordinates to an Yoro string.

Parameters:

Name Type Description Default
lat float

Latitude (WGS 84).

required
lon float

Longitude (WGS 84).

required
precision int

Hilbert order (higher = finer grid). Default 12.

12
domain str

ISO country code or "XX" for global.

'CI'

Returns:

Type Description
str

Code string, e.g. "CI-4H7A3B".

Raises:

Type Description
ValueError

If domain is unknown, or if the coordinates fall outside the domain's bounding box.

Source code in src/yoro/codec.py
def encode(lat: float, lon: float, precision: int = 12, domain: str = "CI") -> str:
    """Encode GPS coordinates to an Yoro string.

    Args:
        lat: Latitude (WGS 84).
        lon: Longitude (WGS 84).
        precision: Hilbert order (higher = finer grid). Default 12.
        domain: ISO country code or "XX" for global.

    Returns:
        Code string, e.g. ``"CI-4H7A3B"``.

    Raises:
        ValueError: If *domain* is unknown, or if the coordinates fall
            outside the domain's bounding box.
    """
    if domain not in DOMAINS:
        raise ValueError(f"Unknown domain: '{domain}'. Available: {list(DOMAINS.keys())}")

    dom = DOMAINS[domain]
    if not (dom["lat_min"] <= lat <= dom["lat_max"] and dom["lon_min"] <= lon <= dom["lon_max"]):
        raise ValueError(
            f"Coordinates ({lat}, {lon}) are outside domain '{domain}' "
            f"(lat {dom['lat_min']}..{dom['lat_max']}, lon {dom['lon_min']}..{dom['lon_max']}). "
            f"Use domain='XX' for worldwide coverage."
        )

    k = _code_length(precision)
    p = _canonical_precision(k)
    m = 1 << p

    # Clamp only the exact-upper-bound edge (lat == lat_max maps to cell m-1)
    x = min(m - 1, int((lon - dom["lon_min"]) / (dom["lon_max"] - dom["lon_min"]) * m))
    y = min(m - 1, int((lat - dom["lat_min"]) / (dom["lat_max"] - dom["lat_min"]) * m))

    d = _xy2d(p, x, y)
    code = _int_to_base29(d, k)

    return f"{domain}-{code}"

encode_for_country(lat, lon, country_code, precision=12)

Encode a point using its country's domain, falling back worldwide.

The country-specific domains are bounding boxes, so a point can be legitimately in a country and outside its box: border areas, enclaves, offshore points, and countries covered by a combined extract. A country may also have no domain at all. Either way the point stays addressable — this falls back to the planet-wide "XX" domain instead of raising.

Prefer this over encode(lat, lon, domain=domain_for_country(cc)) whenever the coordinates are not known to sit inside the box: that form raises, and every caller ends up reimplementing the same fallback.

Parameters:

Name Type Description Default
lat float

Latitude in degrees.

required
lon float

Longitude in degrees.

required
country_code str | None

ISO 3166-1 alpha-2 code, e.g. "CI". None or an unknown code goes straight to the worldwide domain.

required
precision int

Hilbert order; see :func:precision_levels.

12

Returns:

Type Description
str

Code string, in the country's domain when it fits, "XX-…" otherwise.

Example::

>>> encode_for_country(6.8, -5.3, "CI")     # inside the box
'CI-NW64D'
>>> encode_for_country(64.1, -21.9, "CI")   # Reykjavik is not
'XX-...'
Source code in src/yoro/codec.py
def encode_for_country(
    lat: float,
    lon: float,
    country_code: str | None,
    precision: int = 12,
) -> str:
    """Encode a point using its country's domain, falling back worldwide.

    The country-specific domains are bounding boxes, so a point can be
    legitimately *in* a country and *outside* its box: border areas, enclaves,
    offshore points, and countries covered by a combined extract. A country may
    also have no domain at all. Either way the point stays addressable — this
    falls back to the planet-wide ``"XX"`` domain instead of raising.

    Prefer this over ``encode(lat, lon, domain=domain_for_country(cc))``
    whenever the coordinates are not known to sit inside the box: that form
    raises, and every caller ends up reimplementing the same fallback.

    Args:
        lat: Latitude in degrees.
        lon: Longitude in degrees.
        country_code: ISO 3166-1 alpha-2 code, e.g. ``"CI"``. ``None`` or an
            unknown code goes straight to the worldwide domain.
        precision: Hilbert order; see :func:`precision_levels`.

    Returns:
        Code string, in the country's domain when it fits, ``"XX-…"`` otherwise.

    Example::

        >>> encode_for_country(6.8, -5.3, "CI")     # inside the box
        'CI-NW64D'
        >>> encode_for_country(64.1, -21.9, "CI")   # Reykjavik is not
        'XX-...'
    """
    domain = domain_for_country(country_code)
    try:
        return encode(lat, lon, precision=precision, domain=domain)
    except ValueError:
        if domain == "XX":
            raise
        return encode(lat, lon, precision=precision, domain="XX")

decode(code)

Decode an Yoro string to GPS coordinates and cell bounds.

Parameters:

Name Type Description Default
code str

Yoro string, e.g. "CI-4H7A3B".

required

Returns:

Type Description
dict

Dict with keys: lat, lon, precision, domain, bounds.

Raises:

Type Description
ValueError

If the code format, length, or domain is invalid.

Source code in src/yoro/codec.py
def decode(code: str) -> dict:
    """Decode an Yoro string to GPS coordinates and cell bounds.

    Args:
        code: Yoro string, e.g. ``"CI-4H7A3B"``.

    Returns:
        Dict with keys: ``lat``, ``lon``, ``precision``, ``domain``, ``bounds``.

    Raises:
        ValueError: If the code format, length, or domain is invalid.
    """
    prefix, base29_code = _parse_code(code)

    dom = DOMAINS[prefix]
    k = len(base29_code)
    p = _canonical_precision(k)
    m = 1 << p

    d = _base29_to_int(base29_code)
    x, y = _d2xy(p, d)

    lon = dom["lon_min"] + (x + 0.5) * (dom["lon_max"] - dom["lon_min"]) / m
    lat = dom["lat_min"] + (y + 0.5) * (dom["lat_max"] - dom["lat_min"]) / m

    bounds = _cell_bounds(x, y, p, dom)

    return {
        "lat": round(lat, 8),
        "lon": round(lon, 8),
        "precision": p,
        "domain": prefix,
        "bounds": bounds,
    }

neighbors(code)

Return up to 8 neighboring cell codes (edge/corner adjacency).

Codes on the domain boundary may return fewer than 8 neighbors.

Source code in src/yoro/codec.py
def neighbors(code: str) -> list[str]:
    """Return up to 8 neighboring cell codes (edge/corner adjacency).

    Codes on the domain boundary may return fewer than 8 neighbors.
    """
    prefix, base29_code = _parse_code(code)

    k = len(base29_code)
    p = _canonical_precision(k)
    m = 1 << p

    d = _base29_to_int(base29_code)
    cx, cy = _d2xy(p, d)

    result: list[str] = []
    for dx, dy in [
        (-1, -1), (-1, 0), (-1, 1),
        (0, -1),           (0, 1),
        (1, -1),  (1, 0),  (1, 1),
    ]:
        nx, ny = cx + dx, cy + dy
        if 0 <= nx < m and 0 <= ny < m:
            nd = _xy2d(p, nx, ny)
            ncode = _int_to_base29(nd, k)
            result.append(f"{prefix}-{ncode}")

    return result

resolution(p, domain='CI')

Approximate spatial resolution in meters for Hilbert order p in domain.

Source code in src/yoro/codec.py
def resolution(p: int, domain: str = "CI") -> float:
    """Approximate spatial resolution in meters for Hilbert order *p* in *domain*."""
    dom = DOMAINS[domain]
    lat_range = dom["lat_max"] - dom["lat_min"]
    lon_range = dom["lon_max"] - dom["lon_min"]
    m = 1 << p
    dlat = lat_range / m
    dlon = lon_range / m
    lat_m = dlat * 111_000
    lon_m = dlon * 111_000 * math.cos(math.radians((dom["lat_min"] + dom["lat_max"]) / 2))
    return max(lat_m, lon_m)

get_bounds(code)

Return only the cell bounding box for code.

Source code in src/yoro/codec.py
def get_bounds(code: str) -> dict[str, float]:
    """Return only the cell bounding box for *code*."""
    return decode(code)["bounds"]

cells_in_bounds(lat_min, lat_max, lon_min, lon_max, precision=12, domain='CI', max_cells=2000)

Return all Hilbert cells that intersect a geographic bounding box.

Each item is {"code": "CI-...", "bounds": {...}}.

Raises:

Type Description
ValueError

If domain is unknown, or if the requested area would produce more than max_cells cells (lower the precision or shrink the bounding box).

Source code in src/yoro/codec.py
def cells_in_bounds(
    lat_min: float,
    lat_max: float,
    lon_min: float,
    lon_max: float,
    precision: int = 12,
    domain: str = "CI",
    max_cells: int = 2000,
) -> list[dict]:
    """Return all Hilbert cells that intersect a geographic bounding box.

    Each item is ``{"code": "CI-...", "bounds": {...}}``.

    Raises:
        ValueError: If *domain* is unknown, or if the requested area would
            produce more than *max_cells* cells (lower the precision or
            shrink the bounding box).
    """
    domain = domain.upper()
    dom = DOMAINS.get(domain)
    if not dom:
        raise ValueError(f"Unknown domain: '{domain}'. Available: {list(DOMAINS.keys())}")

    k = _code_length(precision)
    p = _canonical_precision(k)
    m = 1 << p

    lat_range = dom["lat_max"] - dom["lat_min"]
    lon_range = dom["lon_max"] - dom["lon_min"]
    lat_step = lat_range / m
    lon_step = lon_range / m

    y_min = max(0, int((lat_min - dom["lat_min"]) / lat_step))
    y_max = min(m - 1, int((lat_max - dom["lat_min"]) / lat_step))
    x_min = max(0, int((lon_min - dom["lon_min"]) / lon_step))
    x_max = min(m - 1, int((lon_max - dom["lon_min"]) / lon_step))

    count = (x_max - x_min + 1) * (y_max - y_min + 1)
    if count <= 0:
        return []
    if count > max_cells:
        raise ValueError(
            f"Bounding box would produce {count} cells (max_cells={max_cells}). "
            f"Lower the precision or shrink the box."
        )

    cells: list[dict] = []
    for y in range(y_min, y_max + 1):
        for x in range(x_min, x_max + 1):
            d = _xy2d(p, x, y)
            cells.append({
                "code": f"{domain}-{_int_to_base29(d, k)}",
                "bounds": {
                    "lat_min": dom["lat_min"] + y * lat_step,
                    "lat_max": dom["lat_min"] + (y + 1) * lat_step,
                    "lon_min": dom["lon_min"] + x * lon_step,
                    "lon_max": dom["lon_min"] + (x + 1) * lon_step,
                },
            })
    return cells

precision_levels(domain='CI', max_code_length=10)

Return all canonical precision levels for a domain.

A canonical precision is a Hilbert order p that produces a distinct code length k. Because codes use base-29, the mapping from p to k is k = ceil(2p * log2 / log29). Several consecutive values of p map to the same k — only the highest p for each k is canonical, i.e. the one that fully exploits the address space of k characters.

Parameters:

Name Type Description Default
domain str

ISO country code (affects resolution in meters).

'CI'
max_code_length int

Stop after this many characters (default 10 → ~4 cm).

10

Returns:

Type Description
list[dict]

List of dicts with keys: precision, code_length, grid_size,

list[dict]

total_cells, resolution_m.

Source code in src/yoro/codec.py
def precision_levels(domain: str = "CI", max_code_length: int = 10) -> list[dict]:
    """Return all canonical precision levels for a domain.

    A canonical precision is a Hilbert order *p* that produces a distinct
    code length *k*.  Because codes use base-29, the mapping from *p* to *k*
    is ``k = ceil(2p * log2 / log29)``.  Several consecutive values of *p*
    map to the same *k* — only the highest *p* for each *k* is canonical,
    i.e. the one that fully exploits the address space of *k* characters.

    Args:
        domain: ISO country code (affects resolution in meters).
        max_code_length: Stop after this many characters (default 10 → ~4 cm).

    Returns:
        List of dicts with keys: ``precision``, ``code_length``, ``grid_size``,
        ``total_cells``, ``resolution_m``.
    """
    if domain not in DOMAINS:
        raise ValueError(f"Unknown domain: '{domain}'")

    levels: list[dict] = []
    for k in range(1, max_code_length + 1):
        p = _canonical_precision(k)
        grid = 1 << p
        res = resolution(p, domain=domain)
        levels.append({
            "precision": p,
            "code_length": k,
            "grid_size": grid,
            "total_cells": grid * grid,
            "resolution_m": round(res, 4),
        })
    return levels

snap_precision(p)

Return the canonical precision that p actually resolves to.

Because the code length is quantized to whole base-29 characters, several values of p produce the same grid. This function shows which canonical precision is effectively used.

Example::

>>> snap_precision(18)
19          # p=18 and p=19 both use 8-character codes
>>> snap_precision(15)
17          # p=15 and p=16 both snap up to canonical p=17
Source code in src/yoro/codec.py
def snap_precision(p: int) -> int:
    """Return the canonical precision that *p* actually resolves to.

    Because the code length is quantized to whole base-29 characters,
    several values of *p* produce the same grid.  This function shows
    which canonical precision is effectively used.

    Example::

        >>> snap_precision(18)
        19          # p=18 and p=19 both use 8-character codes
        >>> snap_precision(15)
        17          # p=15 and p=16 both snap up to canonical p=17
    """
    k = _code_length(p)
    return _canonical_precision(k)

domain_for_country(country_code)

Map an ISO country code to an Yoro domain. Falls back to "XX".

Source code in src/yoro/codec.py
def domain_for_country(country_code: str | None) -> str:
    """Map an ISO country code to an Yoro domain. Falls back to ``"XX"``."""
    if not country_code:
        return "XX"
    code = country_code.upper()
    return code if code in DOMAINS else "XX"

domain_of(code)

Return the domain a code belongs to, without decoding it.

Cheaper than :func:decode when all you need is the domain — reading the prefix costs a string split, while decoding walks the whole Hilbert curve.

Parameters:

Name Type Description Default
code str

A Yoro code, e.g. "CI-4H7A3B". Case-insensitive.

required

Returns:

Type Description
str

The domain prefix, e.g. "CI".

Raises:

Type Description
ValueError

If the format is invalid or the domain is unknown.

Example::

>>> domain_of("ci-4h7a3b")
'CI'
Source code in src/yoro/codec.py
def domain_of(code: str) -> str:
    """Return the domain a code belongs to, without decoding it.

    Cheaper than :func:`decode` when all you need is the domain — reading the
    prefix costs a string split, while decoding walks the whole Hilbert curve.

    Args:
        code: A Yoro code, e.g. ``"CI-4H7A3B"``. Case-insensitive.

    Returns:
        The domain prefix, e.g. ``"CI"``.

    Raises:
        ValueError: If the format is invalid or the domain is unknown.

    Example::

        >>> domain_of("ci-4h7a3b")
        'CI'
    """
    prefix, _ = _parse_code(code)
    return prefix