Source code for pygeohash.neighbor

"""Geohash neighbor calculation functionality.

This module provides functionality for calculating adjacent geohashes
in different directions (right, left, top, bottom).
"""

from __future__ import annotations

from typing import Dict, Final, Literal

from pygeohash.geohash import __base32
from pygeohash.types import Direction
from pygeohash.logging import get_logger

logger = get_logger(__name__)

# Configuration  -- from https://github.com/davetroy/geohash-js/blob/master/geohash.js
NEIGHBORS: Final[Dict[Direction, Dict[Literal["even", "odd"], str]]] = {
    "right": {
        "even": "bc01fg45238967deuvhjyznpkmstqrwx",
        "odd": "p0r21436x8zb9dcf5h7kjnmqesgutwvy",  # = top-even
    },
    "left": {
        "even": "238967debc01fg45kmstqrwxuvhjyznp",
        "odd": "14365h7k9dcfesgujnmqp0r2twvyx8zb",  # = bottom-even
    },
    "top": {
        "even": "p0r21436x8zb9dcf5h7kjnmqesgutwvy",
        "odd": "bc01fg45238967deuvhjyznpkmstqrwx",  # = right-even
    },
    "bottom": {
        "even": "14365h7k9dcfesgujnmqp0r2twvyx8zb",
        "odd": "238967debc01fg45kmstqrwxuvhjyznp",  # = left-even
    },
}

# Used change of parent tile
BORDERS: Final[Dict[Direction, Dict[Literal["even", "odd"], str]]] = {
    "right": {
        "even": "bcfguvyz",
        "odd": "prxz",  # top-even
    },
    "left": {
        "even": "0145hjnp",
        "odd": "028b",  # bottom-even
    },
    "top": {
        "even": "prxz",
        "odd": "bcfguvyz",  # right-even
    },
    "bottom": {
        "even": "028b",
        "odd": "0145hjnp",  # left-even
    },
}


[docs]def get_adjacent(geohash: str, direction: Direction) -> str: """Calculate the adjacent geohash in the specified direction. Args: geohash (str): The input geohash string. direction (Direction): The direction to find the adjacent geohash. Must be one of: "right", "left", "top", "bottom". Returns: str: The adjacent geohash in the specified direction. Raises: ValueError: If the geohash length is 0 (possible when close to poles). Example: >>> get_adjacent("u4pruyd", "top") 'u4pruyf' """ logger.debug("Finding adjacent geohash for %s in direction %s", geohash, direction) if len(geohash) == 0: logger.error("Cannot find adjacent geohash: input geohash length is 0") raise ValueError("The geohash length cannot be 0. Possible when close to poles") source_hash = geohash.lower() last_char = source_hash[-1] base = source_hash[:-1] logger.debug("Processing: base=%s, last_char=%s", base, last_char) split_direction: Literal["even", "odd"] = "even" if len(source_hash) % 2 == 0 else "odd" logger.debug("Using %s lookup tables based on hash length", split_direction) if last_char in BORDERS[direction][split_direction]: logger.debug("Last character %s is on border, recursively finding parent neighbor", last_char) base = get_adjacent(base, direction) else: logger.debug("Last character %s is not on border, using direct neighbor lookup", last_char) neighbor_str = NEIGHBORS[direction][split_direction] idx = neighbor_str.index(last_char) base32_char = __base32[idx] result = base + base32_char logger.debug("Found adjacent geohash: %s", result) return result