diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 0000000..68da422 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,22 @@ +# This is a format job. Pre-commit has a first-party GitHub action, so we use +# that: https://github.com/pre-commit/action + +name: Format + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +jobs: + pre-commit: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.9" + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml new file mode 100644 index 0000000..50bffbd --- /dev/null +++ b/.github/workflows/pip.yml @@ -0,0 +1,37 @@ +name: Pip + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +jobs: + build: + strategy: + fail-fast: false + matrix: + platform: [ubuntu-20.04, windows-2019, macos-13] + python-version: ["3.9"] + + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Add requirements + run: python -m pip install --upgrade wheel setuptools + + - name: Build and install + run: pip install --verbose .[test] + + - name: Test + if: ${{ startsWith(matrix.platform, 'ubuntu') }} + run: python -m pytest diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..fcdedfe --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,93 @@ +name: Wheels + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + release: + types: + - published + +env: + FORCE_COLOR: 3 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_sdist: + name: Build SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Build SDist + run: pipx run build --sdist + + - name: Check metadata + run: pipx run twine check dist/* + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + + build_wheels: + name: Wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-13, windows-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - uses: pypa/cibuildwheel@v2.17 + env: + CIBW_ARCHS_MACOS: universal2 + CIBW_ARCHS_WINDOWS: auto ARM64 + CIBW_SKIP: pp* *i686 *musllinux* + CIBW_TEST_SKIP: "*" + + - name: Verify clean directory + run: git diff --exit-code + shell: bash + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }} + path: wheelhouse/*.whl + + + upload_all: + name: Upload if release + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + environment: pypi + permissions: + id-token: write + + steps: + - uses: actions/setup-python@v5 + with: + python-version: "3.9" + + - uses: actions/download-artifact@v4 + with: + pattern: cibw-* + merge-multiple: true + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.pypi_password }} diff --git a/.gitignore b/.gitignore index c354b12..6a03afb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ _generate/ *env* wheelhouse !test.py +stubs diff --git a/.gitmodules b/.gitmodules index 2d43709..0bb58c4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +1,3 @@ -[submodule "pybind11"] - path = pybind11 - url = https://github.com/pybind/pybind11.git - branch = master [submodule "headers"] path = headers url = https://github.com/cubao/headers.git -[submodule "h3"] - path = h3 - url = https://github.com/uber/h3.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 514eed1..a6ee05d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,40 +32,22 @@ repos: - id: mixed-line-ending - id: requirements-txt-fixer - id: trailing-whitespace - - id: end-of-file-fixer - -# Black, the code formatter, natively supports pre-commit -- repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - exclude: ^(docs) -# Sort your imports in a standard form -- repo: https://github.com/PyCQA/isort - rev: 5.10.1 +# Check linting and style issues +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.6.5" hooks: - - id: isort - -# Upgrade older Python syntax -- repo: https://github.com/asottile/pyupgrade - rev: v2.31.1 - hooks: - - id: pyupgrade - args: ["--py36-plus"] + - id: ruff + args: ["--fix", "--show-fixes"] + - id: ruff-format + exclude: ^(docs) # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.13 hooks: - id: remove-tabs - exclude: ^(docs|Makefile) - -- repo: https://github.com/PyCQA/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - additional_dependencies: [flake8-bugbear] + exclude: ^(docs|Makefile|benchmarks/Makefile) # CMake formatting - repo: https://github.com/cheshirekow/cmake-format-precommit @@ -81,3 +63,4 @@ repos: rev: v13.0.0 hooks: - id: clang-format + exclude: ^(src/h3lib) diff --git a/.vscode/settings.json b/.vscode/settings.json index bc3f9cf..2693232 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -95,7 +95,13 @@ "future": "cpp", "regex": "cpp", "core": "cpp", - "jacobi": "cpp" + "jacobi": "cpp", + "__verbose_abort": "cpp", + "charconv": "cpp", + "execution": "cpp", + "latch": "cpp", + "shared_mutex": "cpp", + "span": "cpp" }, "workbench.colorCustomizations": { "activityBar.background": "#143328", diff --git a/CMakeLists.txt b/CMakeLists.txt index 5656fc2..5ae78cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,11 @@ -cmake_minimum_required(VERSION 3.4...3.18) -project(geocondense) +cmake_minimum_required(VERSION 3.15...3.27) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) +project( + ${SKBUILD_PROJECT_NAME} + VERSION ${SKBUILD_PROJECT_VERSION} + LANGUAGES C CXX) +# set(CMAKE_BUILD_TYPE "Debug") if(NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") set(CMAKE_BUILD_TYPE "Release" @@ -20,19 +22,20 @@ elseif(CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") endif() -include_directories( - ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/h3/src/h3lib/include - ${PROJECT_SOURCE_DIR}/headers/include) - -configure_file(h3/src/h3lib/include/h3api.h.in ${PROJECT_BINARY_DIR}/h3api.h) -file(GLOB H3SOURCES h3/src/h3lib/lib/*.c) -# add_library(h3 STATIC ${H3SOURCES}) - +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) -set(PYBIND11_CPP_STANDARD -std=c++17) -add_subdirectory(pybind11) -pybind11_add_module(pybind11_geocondense src/main.cpp ${H3SOURCES}) +include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/headers/include) +include_directories(${PROJECT_BINARY_DIR} + ${PROJECT_SOURCE_DIR}/src/h3lib/include) + +configure_file(src/h3lib/include/h3api.h.in ${PROJECT_BINARY_DIR}/h3api.h) +file(GLOB H3SOURCES src/h3lib/lib/*.c) -# target_link_libraries(pybind11_geocondense h3) -target_compile_definitions(pybind11_geocondense - PRIVATE VERSION_INFO=${EXAMPLE_VERSION_INFO}) +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) +find_package(pybind11 CONFIG REQUIRED) +python_add_library(_core MODULE src/main.cpp ${H3SOURCES} WITH_SOABI) +target_link_libraries(_core PRIVATE pybind11::headers) +target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION}) +install(TARGETS _core DESTINATION ${PROJECT_NAME}) diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 70bafe6..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,7 +0,0 @@ -include README.md LICENSE pybind11/LICENSE -graft headers -graft pybind11/include -graft pybind11/tools -graft src -graft tests -global-include CMakeLists.txt *.cmake diff --git a/Makefile b/Makefile index c100dbf..47fa01d 100644 --- a/Makefile +++ b/Makefile @@ -11,14 +11,10 @@ lint: pre-commit run -a lint_install: pre-commit install - -build: - mkdir -p build && cd build && \ - cmake .. && make -.PHONY: build +.PHONY: lint lint_install DOCKER_TAG_WINDOWS ?= ghcr.io/cubao/build-env-windows-x64:v0.0.1 -DOCKER_TAG_LINUX ?= ghcr.io/cubao/build-env-manylinux2014-x64:v0.0.1 +DOCKER_TAG_LINUX ?= ghcr.io/cubao/build-env-manylinux2014-x64:v0.0.5 DOCKER_TAG_MACOS ?= ghcr.io/cubao/build-env-macos-arm64:v0.0.1 test_in_arg ?= --rm --name geocondense -w `pwd` -v `pwd`:`pwd` @@ -30,40 +26,46 @@ test_in_linux: docker run $(test_in_arg) -v `pwd`/build/linux:`pwd`/build -it $(DOCKER_TAG_LINUX) bash PYTHON ?= python3 +build: + $(PYTHON) -m pip install scikit_build_core pyproject_metadata pathspec pybind11 + CMAKE_BUILD_PARALLEL_LEVEL=$(NUM_JOBS) $(PYTHON) -m pip install --no-build-isolation -Ceditable.rebuild=true -Cbuild-dir=build -ve. python_install: - $(PYTHON) setup.py install -python_fast_install: - $(PYTHON) setup.py install --no-deps -python_build: - $(PYTHON) setup.py bdist_wheel + $(PYTHON) -m pip install . --verbose +python_wheel: + $(PYTHON) -m pip wheel . -w dist --verbose +python_build: python_wheel python_sdist: - $(PYTHON) setup.py sdist - # tar -tvf dist/geocondense-*.tar.gz + $(PYTHON) -m pip sdist . --verbose python_test: - pytest --capture=tee-sys tests -test: python_test + $(PYTHON) -m pip install pytest + pytest tests +.PHONY: build python_install python_wheel python_build python_sdist python_test + +restub: + pybind11-stubgen geocondense._core -o stubs + cp stubs/geocondense/_core.pyi src/geocondense -# conda create -y -n py36 python=3.6 -# conda create -y -n py37 python=3.7 # conda create -y -n py38 python=3.8 # conda create -y -n py39 python=3.9 # conda create -y -n py310 python=3.10 +# conda create -y -n py311 python=3.11 +# conda create -y -n py312 python=3.12 # conda env list -python_build_py36: - PYTHON=python conda run --no-capture-output -n py36 make python_build -python_build_py37: - PYTHON=python conda run --no-capture-output -n py37 make python_build python_build_py38: PYTHON=python conda run --no-capture-output -n py38 make python_build python_build_py39: PYTHON=python conda run --no-capture-output -n py39 make python_build python_build_py310: PYTHON=python conda run --no-capture-output -n py310 make python_build -python_build_all: python_build_py36 python_build_py37 python_build_py38 python_build_py39 python_build_py310 +python_build_py311: + PYTHON=python conda run --no-capture-output -n py311 make python_build +python_build_py312: + PYTHON=python conda run --no-capture-output -n py312 make python_build +python_build_all: python_build_py38 python_build_py39 python_build_py310 python_build_py311 python_build_py312 python_build_all_in_linux: docker run --rm -w `pwd` -v `pwd`:`pwd` -v `pwd`/build/win:`pwd`/build -it $(DOCKER_TAG_LINUX) make python_build_all make repair_wheels && rm -rf dist/*.whl && mv wheelhouse/*.whl dist && rm -rf wheelhouse -python_build_all_in_macos: python_build_py38 python_build_py39 python_build_py310 +python_build_all_in_macos: python_build_py38 python_build_py39 python_build_py310 python_build_py311 python_build_py312 python_build_all_in_windows: python_build_all repair_wheels: diff --git a/geocondense/__init__.py b/geocondense/__init__.py deleted file mode 100644 index bdb65f2..0000000 --- a/geocondense/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from pybind11_geocondense import * # noqa -from pybind11_geocondense import __version__ # noqa diff --git a/h3 b/h3 deleted file mode 160000 index 53b52ae..0000000 --- a/h3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 53b52ae098ba3c808d3d8190c9c2746438c859df diff --git a/headers b/headers index a63dc6a..36d9779 160000 --- a/headers +++ b/headers @@ -1 +1 @@ -Subproject commit a63dc6a7191f456737f8901abe14834fe0c5d664 +Subproject commit 36d97791d09bb4a27de04853b4b00a2a9ceee1f7 diff --git a/pybind11 b/pybind11 deleted file mode 160000 index 914c06f..0000000 --- a/pybind11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 914c06fb252b6cc3727d0eedab6736e88a3fcb01 diff --git a/pyproject.toml b/pyproject.toml index 7b3b581..f9ef53c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,25 +1,117 @@ [build-system] -requires = [ - "setuptools>=42", - "wheel", - "ninja", - "cmake>=3.12", +requires = ["scikit-build-core>=0.3.3", "pybind11"] +build-backend = "scikit_build_core.build" + + +[project] +name = "geocondense" +version = "0.0.4" +url = "https://github.com/cubao/geocondense" +description="geocondense" +readme = "README.md" +authors = [ + { name = "district10", email = "dvorak4tzx@gmail.com" }, +] +requires-python = ">=3.7" +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "concave_hull", + "fire", + "loguru", + "numpy", + "open3d", + "polyline_ruler", ] -build-backend = "setuptools.build_meta" -[tool.isort] -profile = "black" + +[project.optional-dependencies] +test = ["pytest"] + + +[tool.scikit-build] +wheel.expand-macos-universal-tags = true + [tool.pytest.ini_options] minversion = "6.0" addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] xfail_strict = true -filterwarnings = ["error"] +log_cli_level = "INFO" +filterwarnings = [ + "error", +] testpaths = ["tests"] + [tool.cibuildwheel] test-command = "pytest {project}/tests" test-extras = ["test"] test-skip = ["*universal2:arm64"] -# Setuptools bug causes collision between pypy and cpython artifacts -before-build = "rm -rf {project}/build" +build-verbosity = 1 + + +[tool.ruff] +src = ["src"] + +[tool.ruff.lint] +exclude = ["*.pyi"] +extend-select = [ + "B", # flake8-bugbear + "I", # isort + "ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "RET", # flake8-return + "RUF", # Ruff-specific + "SIM", # flake8-simplify + "T20", # flake8-print + "UP", # pyupgrade + "YTT", # flake8-2020 + "EXE", # flake8-executable + "NPY", # NumPy specific rules + "PD", # pandas-vet +] +ignore = [ + "ARG001", + "NPY002", + "PLR09", # Too many X + "PLR2004", # Magic comparison + "PLW2901", + "PT011", + "PT018", + "PTH100", + "PTH103", + "PTH107", + "PTH112", + "PTH113", + "PTH118", + "PTH119", + "PTH120", + "PTH123", + "PTH116", + "PTH207", + "RUF013", + "T201", + "RUF005", +] +isort.required-imports = ["from __future__ import annotations"] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["T20", "E501", "PGH004"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 1f0b992..0000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -concave_hull -fire -loguru -numpy -open3d -polyline_ruler -pybind11_rdp -scipy diff --git a/setup.py b/setup.py deleted file mode 100644 index a63f964..0000000 --- a/setup.py +++ /dev/null @@ -1,144 +0,0 @@ -import os -import re -import subprocess -import sys - -from setuptools import Extension, find_packages, setup -from setuptools.command.build_ext import build_ext - -# Convert distutils Windows platform specifiers to CMake -A arguments -PLAT_TO_CMAKE = { - "win32": "Win32", - "win-amd64": "x64", - "win-arm32": "ARM", - "win-arm64": "ARM64", -} - - -# A CMakeExtension needs a sourcedir instead of a file list. -# The name must be the _single_ output extension from the CMake build. -# If you need multiple extensions, see scikit-build. -class CMakeExtension(Extension): - def __init__(self, name, sourcedir=""): - Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) - - -class CMakeBuild(build_ext): - def build_extension(self, ext): - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - - # required for auto-detection & inclusion of auxiliary "native" libs - if not extdir.endswith(os.path.sep): - extdir += os.path.sep - - debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug - cfg = "Debug" if debug else "Release" - - # CMake lets you override the generator - we need to check this. - # Can be set with Conda-Build, for example. - cmake_generator = os.environ.get("CMAKE_GENERATOR", "") - - # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON - # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code - # from Python. - cmake_args = [ - f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}", - f"-DPYTHON_EXECUTABLE={sys.executable}", - f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm - ] - build_args = [] - # Adding CMake arguments set as environment variable - # (needed e.g. to build for ARM OSx on conda-forge) - if "CMAKE_ARGS" in os.environ: - cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] - - # In this example, we pass in the version to C++. You might not need to. - cmake_args += [f"-DEXAMPLE_VERSION_INFO={self.distribution.get_version()}"] - - if self.compiler.compiler_type != "msvc": - # Using Ninja-build since it a) is available as a wheel and b) - # multithreads automatically. MSVC would require all variables be - # exported for Ninja to pick it up, which is a little tricky to do. - # Users can override the generator with CMAKE_GENERATOR in CMake - # 3.15+. - if not cmake_generator or cmake_generator == "Ninja": - try: - import ninja # noqa: F401 - - ninja_executable_path = os.path.join(ninja.BIN_DIR, "ninja") - cmake_args += [ - "-GNinja", - f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", - ] - except ImportError: - pass - - else: - - # Single config generators are handled "normally" - single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) - - # CMake allows an arch-in-generator style for backward compatibility - contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) - - # Specify the arch if using MSVC generator, but only if it doesn't - # contain a backward-compatibility arch spec already in the - # generator name. - if not single_config and not contains_arch: - cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] - - # Multi-config generators have a different way to specify configs - if not single_config: - cmake_args += [ - f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}" - ] - build_args += ["--config", cfg] - - if sys.platform.startswith("darwin"): - # Cross-compile support for macOS - respect ARCHFLAGS if set - archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) - if archs: - cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] - - # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level - # across all generators. - if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: - # self.parallel is a Python 3 only way to set parallel jobs by hand - # using -j in the build_ext call, not supported by pip or PyPA-build. - if hasattr(self, "parallel") and self.parallel: - # CMake 3.12+ only. - build_args += [f"-j{self.parallel}"] - - build_temp = os.path.join(self.build_temp, ext.name) - if not os.path.exists(build_temp): - os.makedirs(build_temp) - - subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=build_temp) - subprocess.check_call(["cmake", "--build", "."] + build_args, cwd=build_temp) - - -with open("requirements.txt") as f: - lines = f.readlines() -install_requires = [ - line.strip() for line in lines if line and not line.startswith("--") -] - -# The information here can also be placed in setup.cfg - better separation of -# logic and declaration, and simpler if you include description/version in a file. -setup( - name="geocondense", - version="0.0.3", - author="tzx", - author_email="dvorak4tzx@gmail.com", - url="https://github.com/cubao/geocondense", - description="geocondense", - long_description=open("README.md", encoding="utf-8").read(), - long_description_content_type="text/markdown", - packages=find_packages(), - ext_modules=[CMakeExtension("pybind11_geocondense")], - cmdclass={"build_ext": CMakeBuild}, - zip_safe=False, - install_requires=install_requires, - extras_require={"test": ["pytest>=6.0"]}, -) diff --git a/src/geocondense/__init__.py b/src/geocondense/__init__.py new file mode 100644 index 0000000..5bc82af --- /dev/null +++ b/src/geocondense/__init__.py @@ -0,0 +1,3 @@ +from __future__ import annotations + +from ._core import CondenseOptions, __version__ # noqa: F401 diff --git a/src/geocondense/_core.pyi b/src/geocondense/_core.pyi new file mode 100644 index 0000000..5f1a448 --- /dev/null +++ b/src/geocondense/_core.pyi @@ -0,0 +1,114 @@ +from __future__ import annotations +import typing + +__all__ = ["CondenseOptions", "condense_geojson", "dissect_geojson"] + +class CondenseOptions: + def __init__(self) -> None: + """ + Default constructor for CondenseOptions + """ + @property + def debug(self) -> bool: + """ + Debug option + """ + @debug.setter + def debug(self, arg0: bool) -> None: ... + @property + def douglas_epsilon(self) -> float: + """ + Epsilon value for Douglas-Peucker algorithm + """ + @douglas_epsilon.setter + def douglas_epsilon(self, arg0: float) -> None: ... + @property + def grid_features_keep_properties(self) -> bool: + """ + Option to keep properties for grid features + """ + @grid_features_keep_properties.setter + def grid_features_keep_properties(self, arg0: bool) -> None: ... + @property + def grid_h3_resolution(self) -> int: + """ + H3 resolution for grid features + """ + @grid_h3_resolution.setter + def grid_h3_resolution(self, arg0: int) -> None: ... + @property + def indent(self) -> bool: + """ + Indentation option for JSON output + """ + @indent.setter + def indent(self, arg0: bool) -> None: ... + @property + def sort_keys(self) -> bool: + """ + Option to sort keys in JSON output + """ + @sort_keys.setter + def sort_keys(self, arg0: bool) -> None: ... + @property + def sparsify_h3_resolution(self) -> int: + """ + H3 resolution for sparsification + """ + @sparsify_h3_resolution.setter + def sparsify_h3_resolution(self, arg0: int) -> None: ... + @property + def sparsify_upper_limit(self) -> int: + """ + Upper limit for sparsification + """ + @sparsify_upper_limit.setter + def sparsify_upper_limit(self, arg0: int) -> None: ... + +def condense_geojson( + *, + input_path: str, + output_index_path: str | None = None, + output_strip_path: str | None = None, + output_grids_dir: str | None = None, + options: CondenseOptions = ..., +) -> bool: + """ + Condense GeoJSON data. + + Args: + input_path: Path to the input GeoJSON file. + output_index_path: Optional path for the output index file. + output_strip_path: Optional path for the output strip file. + output_grids_dir: Optional directory for output grid files. + options: CondenseOptions object with configuration options. + + Returns: + bool: True if the operation was successful, False otherwise. + """ + +def dissect_geojson( + *, + input_path: str, + output_geometry: str | None = None, + output_properties: str | None = None, + output_observations: str | None = None, + output_others: str | None = None, + indent: bool = False, +) -> bool: + """ + Dissect GeoJSON data into separate components. + + Args: + input_path: Path to the input GeoJSON file. + output_geometry: Optional path for the output geometry file. + output_properties: Optional path for the output properties file. + output_observations: Optional path for the output observations file. + output_others: Optional path for other output data. + indent: Boolean flag to enable indentation in output JSON. + + Returns: + bool: True if the operation was successful, False otherwise. + """ + +__version__: str = "0.0.3" diff --git a/geocondense/condense.py b/src/geocondense/condense.py similarity index 91% rename from geocondense/condense.py rename to src/geocondense/condense.py index bb18e09..3077e95 100644 --- a/geocondense/condense.py +++ b/src/geocondense/condense.py @@ -1,25 +1,26 @@ +from __future__ import annotations + import argparse import glob import json import os from collections import defaultdict from pathlib import Path -from typing import Any, Dict, List, Optional, Set, Tuple, Union # noqa import open3d as o3d -import polyline_ruler.tf as tf from loguru import logger +from polyline_ruler import tf from geocondense.condense_geojson import condense_geojson from geocondense.condense_pointcloud import condense_pointcloud_impl from geocondense.dissect_geojson import dissect_geojson -from geocondense.utils import read_json, write_json from geocondense.utils import md5sum as default_md5sum +from geocondense.utils import read_json, write_json def resolve_center( - center: Optional[Union[str, Tuple[float, float, float]]] -) -> Optional[Tuple[float, float, float]]: + center: str | tuple[float, float, float] | None, +) -> tuple[float, float, float] | None: if not center: return None if isinstance(center, str): @@ -34,8 +35,8 @@ def default_handle_semantic( *, workdir: str, uuid: str, - center: Optional[Tuple[float, float, float]], -) -> Tuple[str, str]: + center: tuple[float, float, float] | None, +) -> tuple[str, str]: dissect_input_path = path condense_input_path = path return dissect_input_path, condense_input_path @@ -46,7 +47,7 @@ def default_handle_pointcloud( *, workdir: str, uuid: str, - center: Optional[Tuple[float, float, float]], + center: tuple[float, float, float] | None, ) -> o3d.geometry.PointCloud: pcd = o3d.io.read_point_cloud(path) if center: @@ -144,14 +145,14 @@ def condense_pointcloud( def condense( *, workdir: str, - semantic_files: List[str] = None, - pointcloud_files: List[str] = None, - center: Optional[Tuple[float, float, float]] = None, + semantic_files: list[str] = None, + pointcloud_files: list[str] = None, + center: tuple[float, float, float] | None = None, # handlers handle_semantic=default_handle_semantic, handle_pointcloud=default_handle_pointcloud, md5sum=default_md5sum, -) -> Dict[str, str]: +) -> dict[str, str]: assert not ( semantic_files is None and pointcloud_files is None ), "should specify either --semantic_files or --pointcloud_files (or both)" @@ -236,10 +237,10 @@ def main( ) args = parser.parse_args() workdir: str = args.workdir - semantic_files: List[str] = args.semantic_files - pointcloud_files: List[str] = args.pointcloud_files - center: Optional[str] = args.center - export: Optional[str] = args.export + semantic_files: list[str] = args.semantic_files + pointcloud_files: list[str] = args.pointcloud_files + center: str | None = args.center + export: str | None = args.export args = None index = condense( diff --git a/geocondense/condense_geojson.py b/src/geocondense/condense_geojson.py similarity index 87% rename from geocondense/condense_geojson.py rename to src/geocondense/condense_geojson.py index 152befe..537d146 100644 --- a/geocondense/condense_geojson.py +++ b/src/geocondense/condense_geojson.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import os from pprint import pprint -from typing import Any, Dict, List, Optional, Set, Tuple, Union # noqa from geocondense import CondenseOptions -from geocondense import condense_geojson as condense_geojson_impl + +from ._core import condense_geojson as condense_geojson_impl def condense_geojson( @@ -47,8 +49,9 @@ def condense_geojson( options=options, ) if not succ: - pprint(locals()) - raise Exception(f"failed to condense geojson: {input_path}") + pprint(locals()) # noqa: T203 + msg = f"failed to condense geojson: {input_path}" + raise Exception(msg) if __name__ == "__main__": diff --git a/geocondense/condense_pointcloud.py b/src/geocondense/condense_pointcloud.py similarity index 91% rename from geocondense/condense_pointcloud.py rename to src/geocondense/condense_pointcloud.py index 97b57b6..3c0a2cf 100644 --- a/geocondense/condense_pointcloud.py +++ b/src/geocondense/condense_pointcloud.py @@ -1,21 +1,21 @@ +from __future__ import annotations + import json import os from itertools import chain -from typing import Any, Dict, List, Optional, Set, Tuple, Union # noqa import numpy as np import open3d as o3d -import polyline_ruler.tf as tf from concave_hull import concave_hull_indexes from loguru import logger -from pybind11_rdp import rdp_mask +from polyline_ruler import douglas_simplify_mask, tf def condense_pointcloud_impl( *, pcd: o3d.geometry.PointCloud, - output_fence_path: Optional[str] = None, - output_grids_dir: Optional[str] = None, + output_fence_path: str | None = None, + output_grids_dir: str | None = None, grid_resolution: float = 0.0001, compress_pcd: bool = False, ): @@ -103,7 +103,7 @@ def condense_pointcloud_impl( llas = tf.enu2lla(points[concave_hull], anchor_lla=anchor) llas[:, :2] = llas[:, :2].round(6) llas[:, 2] = llas[:, 2].round(1) - mask = rdp_mask(tf.lla2enu(llas), epsilon=0.5).astype(bool) + mask = douglas_simplify_mask(tf.lla2enu(llas), epsilon=0.5).astype(bool) llas = llas[mask] os.makedirs(os.path.dirname(os.path.abspath(output_fence_path)), exist_ok=True) with open(output_fence_path, "w") as f: @@ -136,8 +136,8 @@ def condense_pointcloud_impl( return output_grids_dir = os.path.abspath(output_grids_dir) os.makedirs(output_grids_dir, exist_ok=True) - for ii, (x0, x1) in enumerate(zip(xs[:-1], xs[1:])): # noqa - for jj, (y0, y1) in enumerate(zip(ys[:-1], ys[1:])): # noqa + for ii, (x0, x1) in enumerate(zip(xs[:-1], xs[1:])): + for jj, (y0, y1) in enumerate(zip(ys[:-1], ys[1:])): mask = np.logical_and( np.logical_and(x0 <= xyzs[:, 0], xyzs[:, 0] < x1), np.logical_and(y0 <= xyzs[:, 1], xyzs[:, 1] < y1), @@ -145,7 +145,7 @@ def condense_pointcloud_impl( if not np.any(mask): continue related = [idxes[i] for i in np.where(mask)[0]] - related = sorted(list(chain.from_iterable(related))) + related = sorted(chain.from_iterable(related)) grid = o3d.geometry.PointCloud() grid.points = o3d.utility.Vector3dVector(enus[related]) grid.colors = o3d.utility.Vector3dVector(rgbs[related]) @@ -172,11 +172,11 @@ def condense_pointcloud_impl( def condense_pointcloud( *, input_path: str, - output_fence_path: Optional[str] = None, - output_grids_dir: Optional[str] = None, + output_fence_path: str | None = None, + output_grids_dir: str | None = None, grid_resolution: float = 0.0001, compress_pcd: bool = False, - center: Optional[Tuple[float, float, float]] = None, + center: tuple[float, float, float] | None = None, ): assert ( output_fence_path or output_grids_dir diff --git a/geocondense/dissect_geojson.py b/src/geocondense/dissect_geojson.py similarity index 67% rename from geocondense/dissect_geojson.py rename to src/geocondense/dissect_geojson.py index 27183ce..dabb209 100644 --- a/geocondense/dissect_geojson.py +++ b/src/geocondense/dissect_geojson.py @@ -1,17 +1,18 @@ +from __future__ import annotations + import os from pprint import pprint -from typing import Any, Dict, List, Optional, Set, Tuple, Union # noqa -from geocondense import dissect_geojson as dissect_geojson_impl +from geocondense._core import dissect_geojson as dissect_geojson_impl def dissect_geojson( *, input_path: str, - output_geometry: Optional[str] = None, - output_properties: Optional[str] = None, - output_observations: Optional[str] = None, - output_others: Optional[str] = None, + output_geometry: str | None = None, + output_properties: str | None = None, + output_observations: str | None = None, + output_others: str | None = None, indent: bool = False, ): # mkdir -p @@ -36,8 +37,9 @@ def dissect_geojson( indent=indent, ) if not succ: - pprint(locals()) - raise Exception(f"failed to dissect geojson: {input_path}") + pprint(locals()) # noqa: T203 + msg = f"failed to dissect geojson: {input_path}" + raise Exception(msg) if __name__ == "__main__": diff --git a/src/geocondense/py.typed b/src/geocondense/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/geocondense/utils.py b/src/geocondense/utils.py similarity index 81% rename from geocondense/utils.py rename to src/geocondense/utils.py index e501443..f781eeb 100644 --- a/geocondense/utils.py +++ b/src/geocondense/utils.py @@ -1,13 +1,14 @@ +from __future__ import annotations + import json import os import subprocess from sys import platform -from typing import Any, Dict, List, Optional, Set, Tuple, Union # noqa from loguru import logger -def popen(cmd: Union[str, List[str]]) -> Tuple[int, str, str]: +def popen(cmd: str | list[str]) -> tuple[int, str, str]: if isinstance(cmd, str): cmd = cmd.split(" ") p = subprocess.Popen( @@ -31,18 +32,19 @@ def md5sum(path: str) -> str: assert retcode == 0 md5 = stdout.strip().split()[-1] else: - raise Exception("not ready") + msg = "not implemented on this platform" + raise Exception(msg) assert len(md5) == 32 return md5 -def read_json(path: str) -> Dict: +def read_json(path: str) -> dict: path = os.path.abspath(path) with open(path) as f: return json.load(f) -def write_json(path: str, data: Dict, *, verbose: bool = True) -> str: +def write_json(path: str, data: dict, *, verbose: bool = True) -> str: path = os.path.abspath(path) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w", encoding="utf8") as f: diff --git a/src/h3lib/.DS_Store b/src/h3lib/.DS_Store new file mode 100644 index 0000000..4a6d570 Binary files /dev/null and b/src/h3lib/.DS_Store differ diff --git a/src/h3lib/include/algos.h b/src/h3lib/include/algos.h new file mode 100644 index 0000000..7403438 --- /dev/null +++ b/src/h3lib/include/algos.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016-2018, 2020 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file algos.h + * @brief Hexagon grid algorithms + */ + +#ifndef ALGOS_H +#define ALGOS_H + +#include "bbox.h" +#include "coordijk.h" +#include "h3api.h" +#include "linkedGeo.h" +#include "vertexGraph.h" + +// neighbor along the ijk coordinate system of the current face, rotated +H3Error h3NeighborRotations(H3Index origin, Direction dir, int *rotations, + H3Index *out); + +// IJK direction of neighbor +Direction directionForNeighbor(H3Index origin, H3Index destination); + +// k-ring implementation +void _kRingInternal(H3Index origin, int k, H3Index *out, int *distances, + int maxIdx, int curK); + +// Create a vertex graph from a set of hexagons +H3Error h3SetToVertexGraph(const H3Index *h3Set, const int numHexes, + VertexGraph *out); + +// Create a LinkedGeoPolygon from a vertex graph +void _vertexGraphToLinkedGeo(VertexGraph *graph, LinkedGeoPolygon *out); + +// Internal function for polygonToCells that traces a geoloop with hexagons of +// a specific size +H3Error _getEdgeHexagons(const GeoLoop *geoloop, int64_t numHexagons, int res, + int64_t *numSearchHexes, H3Index *search, + H3Index *found); + +// The safe gridDiskDistances algorithm. +H3Error _gridDiskDistancesInternal(H3Index origin, int k, H3Index *out, + int *distances, int64_t maxIdx, int curK); +#endif diff --git a/src/h3lib/include/alloc.h b/src/h3lib/include/alloc.h new file mode 100644 index 0000000..a40f9ca --- /dev/null +++ b/src/h3lib/include/alloc.h @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file alloc.h + * @brief Memory management functions + * + * This file contains macros and the necessary declarations to be able + * to point H3 at different memory management functions than the standard + * malloc/free/etc functions. + */ + +#ifndef ALLOC_H +#define ALLOC_H + +#include "h3api.h" // for TJOIN + +#ifdef H3_ALLOC_PREFIX +#define H3_MEMORY(name) TJOIN(H3_ALLOC_PREFIX, name) + +#ifdef __cplusplus +extern "C" { +#endif + +void *H3_MEMORY(malloc)(size_t size); +void *H3_MEMORY(calloc)(size_t num, size_t size); +void *H3_MEMORY(realloc)(void *ptr, size_t size); +void H3_MEMORY(free)(void *ptr); + +#ifdef __cplusplus +} +#endif + +#else +#define H3_MEMORY(name) name +#endif + +#endif diff --git a/src/h3lib/include/baseCells.h b/src/h3lib/include/baseCells.h new file mode 100644 index 0000000..653b47b --- /dev/null +++ b/src/h3lib/include/baseCells.h @@ -0,0 +1,62 @@ +/* + * Copyright 2016-2018 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file baseCells.h + * @brief Base cell related lookup tables and access functions. + */ + +#ifndef BASECELLS_H +#define BASECELLS_H + +#include "constants.h" +#include "coordijk.h" +#include "faceijk.h" + +/** @struct BaseCellData + * @brief information on a single base cell + */ +typedef struct { + FaceIJK + homeFijk; ///< "home" face and normalized ijk coordinates on that face + int isPentagon; ///< is this base cell a pentagon? + int cwOffsetPent[2]; ///< if a pentagon, what are its two clockwise offset + /// faces? +} BaseCellData; + +#define INVALID_BASE_CELL 127 +extern const int baseCellNeighbors[NUM_BASE_CELLS][7]; +extern const int baseCellNeighbor60CCWRots[NUM_BASE_CELLS][7]; + +// resolution 0 base cell data lookup-table (global) +extern const BaseCellData baseCellData[NUM_BASE_CELLS]; + +/** Maximum input for any component to face-to-base-cell lookup functions */ +#define MAX_FACE_COORD 2 + +/** Invalid number of rotations */ +#define INVALID_ROTATIONS -1 + +// Internal functions +int _isBaseCellPentagon(int baseCell); +bool _isBaseCellPolarPentagon(int baseCell); +int _faceIjkToBaseCell(const FaceIJK *h); +int _faceIjkToBaseCellCCWrot60(const FaceIJK *h); +int _baseCellToCCWrot60(int baseCell, int face); +void _baseCellToFaceIjk(int baseCell, FaceIJK *h); +bool _baseCellIsCwOffset(int baseCell, int testFace); +int _getBaseCellNeighbor(int baseCell, Direction dir); +Direction _getBaseCellDirection(int originBaseCell, int destinationBaseCell); + +#endif diff --git a/src/h3lib/include/bbox.h b/src/h3lib/include/bbox.h new file mode 100644 index 0000000..e81912b --- /dev/null +++ b/src/h3lib/include/bbox.h @@ -0,0 +1,45 @@ +/* + * Copyright 2016-2017, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file bbox.h + * @brief Geographic bounding box functions + */ + +#ifndef BBOX_H +#define BBOX_H + +#include + +#include "latLng.h" + +/** @struct BBox + * @brief Geographic bounding box with coordinates defined in radians + */ +typedef struct { + double north; ///< north latitude + double south; ///< south latitude + double east; ///< east longitude + double west; ///< west longitude +} BBox; + +bool bboxIsTransmeridian(const BBox *bbox); +void bboxCenter(const BBox *bbox, LatLng *center); +bool bboxContains(const BBox *bbox, const LatLng *point); +bool bboxEquals(const BBox *b1, const BBox *b2); +H3Error bboxHexEstimate(const BBox *bbox, int res, int64_t *out); +H3Error lineHexEstimate(const LatLng *origin, const LatLng *destination, + int res, int64_t *out); + +#endif diff --git a/src/h3lib/include/constants.h b/src/h3lib/include/constants.h new file mode 100644 index 0000000..db53ada --- /dev/null +++ b/src/h3lib/include/constants.h @@ -0,0 +1,86 @@ +/* + * Copyright 2016-2017, 2020 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file constants.h + * @brief Constants used by more than one source code file. + */ + +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#ifndef M_PI +/** pi */ +#define M_PI 3.14159265358979323846 +#endif + +#ifndef M_PI_2 +/** pi / 2.0 */ +#define M_PI_2 1.5707963267948966 +#endif + +/** 2.0 * PI */ +#define M_2PI 6.28318530717958647692528676655900576839433L + +/** pi / 180 */ +#define M_PI_180 0.0174532925199432957692369076848861271111L +/** 180 / pi */ +#define M_180_PI 57.29577951308232087679815481410517033240547L + +/** threshold epsilon */ +#define EPSILON 0.0000000000000001L +/** sqrt(3) / 2.0 */ +#define M_SQRT3_2 0.8660254037844386467637231707529361834714L +/** sin(60') */ +#define M_SIN60 M_SQRT3_2 + +/** rotation angle between Class II and Class III resolution axes + * (asin(sqrt(3.0 / 28.0))) */ +#define M_AP7_ROT_RADS 0.333473172251832115336090755351601070065900389L + +/** sin(M_AP7_ROT_RADS) */ +#define M_SIN_AP7_ROT 0.3273268353539885718950318L + +/** cos(M_AP7_ROT_RADS) */ +#define M_COS_AP7_ROT 0.9449111825230680680167902L + +/** earth radius in kilometers using WGS84 authalic radius */ +#define EARTH_RADIUS_KM 6371.007180918475L + +/** scaling factor from hex2d resolution 0 unit length + * (or distance between adjacent cell center points + * on the plane) to gnomonic unit length. */ +#define RES0_U_GNOMONIC 0.38196601125010500003L + +/** max H3 resolution; H3 version 1 has 16 resolutions, numbered 0 through 15 */ +#define MAX_H3_RES 15 + +/** The number of faces on an icosahedron */ +#define NUM_ICOSA_FACES 20 +/** The number of H3 base cells */ +#define NUM_BASE_CELLS 122 +/** The number of vertices in a hexagon */ +#define NUM_HEX_VERTS 6 +/** The number of vertices in a pentagon */ +#define NUM_PENT_VERTS 5 +/** The number of pentagons per resolution **/ +#define NUM_PENTAGONS 12 + +/** H3 index modes */ +#define H3_CELL_MODE 1 +#define H3_DIRECTEDEDGE_MODE 2 +#define H3_EDGE_MODE 3 +#define H3_VERTEX_MODE 4 + +#endif diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h new file mode 100644 index 0000000..422fdb1 --- /dev/null +++ b/src/h3lib/include/coordijk.h @@ -0,0 +1,118 @@ +/* + * Copyright 2016-2018, 2020-2022 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file coordijk.h + * @brief Header file for CoordIJK functions including conversion from lat/lng + * + * References two Vec2d cartesian coordinate systems: + * + * 1. gnomonic: face-centered polyhedral gnomonic projection space with + * traditional scaling and x-axes aligned with the face Class II + * i-axes. + * + * 2. hex2d: local face-centered coordinate system scaled a specific H3 grid + * resolution unit length and with x-axes aligned with the local + * i-axes + */ + +#ifndef COORDIJK_H +#define COORDIJK_H + +#include "h3api.h" +#include "latLng.h" +#include "vec2d.h" + +/** @struct CoordIJK + * @brief IJK hexagon coordinates + * + * Each axis is spaced 120 degrees apart. + */ +typedef struct { + int i; ///< i component + int j; ///< j component + int k; ///< k component +} CoordIJK; + +/** @brief CoordIJK unit vectors corresponding to the 7 H3 digits. + */ +static const CoordIJK UNIT_VECS[] = { + {0, 0, 0}, // direction 0 + {0, 0, 1}, // direction 1 + {0, 1, 0}, // direction 2 + {0, 1, 1}, // direction 3 + {1, 0, 0}, // direction 4 + {1, 0, 1}, // direction 5 + {1, 1, 0} // direction 6 +}; + +/** @brief H3 digit representing ijk+ axes direction. + * Values will be within the lowest 3 bits of an integer. + */ +typedef enum { + /** H3 digit in center */ + CENTER_DIGIT = 0, + /** H3 digit in k-axes direction */ + K_AXES_DIGIT = 1, + /** H3 digit in j-axes direction */ + J_AXES_DIGIT = 2, + /** H3 digit in j == k direction */ + JK_AXES_DIGIT = J_AXES_DIGIT | K_AXES_DIGIT, /* 3 */ + /** H3 digit in i-axes direction */ + I_AXES_DIGIT = 4, + /** H3 digit in i == k direction */ + IK_AXES_DIGIT = I_AXES_DIGIT | K_AXES_DIGIT, /* 5 */ + /** H3 digit in i == j direction */ + IJ_AXES_DIGIT = I_AXES_DIGIT | J_AXES_DIGIT, /* 6 */ + /** H3 digit in the invalid direction */ + INVALID_DIGIT = 7, + /** Valid digits will be less than this value. Same value as INVALID_DIGIT. + */ + NUM_DIGITS = INVALID_DIGIT, + /** Child digit which is skipped for pentagons */ + PENTAGON_SKIPPED_DIGIT = K_AXES_DIGIT /* 1 */ +} Direction; + +// Internal functions + +void _setIJK(CoordIJK *ijk, int i, int j, int k); +void _hex2dToCoordIJK(const Vec2d *v, CoordIJK *h); +void _ijkToHex2d(const CoordIJK *h, Vec2d *v); +int _ijkMatches(const CoordIJK *c1, const CoordIJK *c2); +void _ijkAdd(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *sum); +void _ijkSub(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *diff); +void _ijkScale(CoordIJK *c, int factor); +bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk); +void _ijkNormalize(CoordIJK *c); +Direction _unitIjkToDigit(const CoordIJK *ijk); +H3Error _upAp7Checked(CoordIJK *ijk); +H3Error _upAp7rChecked(CoordIJK *ijk); +void _upAp7(CoordIJK *ijk); +void _upAp7r(CoordIJK *ijk); +void _downAp7(CoordIJK *ijk); +void _downAp7r(CoordIJK *ijk); +void _downAp3(CoordIJK *ijk); +void _downAp3r(CoordIJK *ijk); +void _neighbor(CoordIJK *ijk, Direction digit); +void _ijkRotate60ccw(CoordIJK *ijk); +void _ijkRotate60cw(CoordIJK *ijk); +Direction _rotate60ccw(Direction digit); +Direction _rotate60cw(Direction digit); +int ijkDistance(const CoordIJK *a, const CoordIJK *b); +void ijkToIj(const CoordIJK *ijk, CoordIJ *ij); +H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk); +void ijkToCube(CoordIJK *ijk); +void cubeToIjk(CoordIJK *ijk); + +#endif diff --git a/src/h3lib/include/directedEdge.h b/src/h3lib/include/directedEdge.h new file mode 100644 index 0000000..89da4a2 --- /dev/null +++ b/src/h3lib/include/directedEdge.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017, 2020 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file directedEdge.h + * @brief DirectedEdge functions for manipulating directed edge indexes. + */ + +#ifndef DIRECTEDEDGE_H +#define DIRECTEDEDGE_H + +#include "algos.h" +#include "h3Index.h" + +// nothing non-public in this file + +#endif diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h new file mode 100644 index 0000000..c05cbf2 --- /dev/null +++ b/src/h3lib/include/faceijk.h @@ -0,0 +1,90 @@ +/* + * Copyright 2016-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file faceijk.h + * @brief FaceIJK functions including conversion to/from lat/lng. + * + * References the Vec2d cartesian coordinate systems hex2d: local face-centered + * coordinate system scaled a specific H3 grid resolution unit length and + * with x-axes aligned with the local i-axes + */ + +#ifndef FACEIJK_H +#define FACEIJK_H + +#include "coordijk.h" +#include "latLng.h" +#include "vec2d.h" + +/** @struct FaceIJK + * @brief Face number and ijk coordinates on that face-centered coordinate + * system + */ +typedef struct { + int face; ///< face number + CoordIJK coord; ///< ijk coordinates on that face +} FaceIJK; + +/** @struct FaceOrientIJK + * @brief Information to transform into an adjacent face IJK system + */ +typedef struct { + int face; ///< face number + CoordIJK translate; ///< res 0 translation relative to primary face + int ccwRot60; ///< number of 60 degree ccw rotations relative to primary + /// face +} FaceOrientIJK; + +extern const LatLng faceCenterGeo[NUM_ICOSA_FACES]; + +// indexes for faceNeighbors table +/** IJ quadrant faceNeighbors table direction */ +#define IJ 1 +/** KI quadrant faceNeighbors table direction */ +#define KI 2 +/** JK quadrant faceNeighbors table direction */ +#define JK 3 + +/** Invalid face index */ +#define INVALID_FACE -1 + +/** Digit representing overage type */ +typedef enum { + /** No overage (on original face) */ + NO_OVERAGE = 0, + /** On face edge (only occurs on substrate grids) */ + FACE_EDGE = 1, + /** Overage on new face interior */ + NEW_FACE = 2 +} Overage; + +// Internal functions + +void _geoToFaceIjk(const LatLng *g, int res, FaceIJK *h); +void _geoToHex2d(const LatLng *g, int res, int *face, Vec2d *v); +void _faceIjkToGeo(const FaceIJK *h, int res, LatLng *g); +void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, + CellBoundary *g); +void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, + int length, CellBoundary *g); +void _faceIjkToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts); +void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts); +void _hex2dToGeo(const Vec2d *v, int face, int res, int substrate, LatLng *g); +Overage _adjustOverageClassII(FaceIJK *fijk, int res, int pentLeading4, + int substrate); +Overage _adjustPentVertOverage(FaceIJK *fijk, int res); +void _geoToClosestFace(const LatLng *g, int *face, double *sqd); + +#endif diff --git a/src/h3lib/include/h3Assert.h b/src/h3lib/include/h3Assert.h new file mode 100644 index 0000000..125cc5a --- /dev/null +++ b/src/h3lib/include/h3Assert.h @@ -0,0 +1,125 @@ +/* + * Copyright 2022 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file h3Assert.h + * @brief Support code for unit testing and assertions + * + * This file defines macros needed for defensive programming in the H3 core + * library. H3 strives to have complete code and branch coverage, but this is + * not feasible if some branches cannot be reached because they are defensive - + * that is, we do not know of a test case that would exercise the branch but we + * do have an opinion of how to recover from such an error. These defensive + * branches are excluded from coverage. + * + * In other testing, such as unit tests or fuzzer testing, they trigger + * assertions if the conditions fail. + * + * Adapted from https://www.sqlite.org/testing.html and + * https://www.sqlite.org/assert.html + * + * Used under the terms of the SQLite3 project, reproduced below: + * The author disclaims copyright to this source code. In place of + * a legal notice, here is a blessing: + * + * May you do good and not evil. + * May you find forgiveness for yourself and forgive others. + * May you share freely, never taking more than you give. + */ + +#ifndef H3ASSERT_H +#define H3ASSERT_H + +#include + +/* +** The testcase() macro is used to aid in coverage testing. When +** doing coverage testing, the condition inside the argument to +** testcase() must be evaluated both true and false in order to +** get full branch coverage. The testcase() macro is inserted +** to help ensure adequate test coverage in places where simple +** condition/decision coverage is inadequate. For example, testcase() +** can be used to make sure boundary values are tested. For +** bitmask tests, testcase() can be used to make sure each bit +** is significant and used at least once. On switch statements +** where multiple cases go to the same block of code, testcase() +** can insure that all cases are evaluated. +*/ +#if defined(H3_COVERAGE_TEST) || defined(H3_DEBUG) +extern unsigned int h3CoverageCounter; +#define testcase(X) \ + if (X) { \ + h3CoverageCounter += (unsigned)__LINE__; \ + } +#else +#define testcase(X) +#endif + +/* +** Disable ALWAYS() and NEVER() (make them pass-throughs) for coverage +** and mutation testing +*/ +#if defined(H3_COVERAGE_TEST) +#define H3_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif + +/* +** The TESTONLY macro is used to enclose variable declarations or +** other bits of code that are needed to support the arguments +** within testcase() and assert() macros. +*/ +#if !defined(NDEBUG) || defined(H3_COVERAGE_TEST) +#define TESTONLY(X) X +#else +#define TESTONLY(X) +#endif + +/* +** The DEFENSEONLY macro is used to enclose variable declarations or +** other bits of code that are needed to support the arguments +** within ALWAYS() or NEVER() macros. +*/ +#if !defined(H3_OMIT_AUXILIARY_SAFETY_CHECKS) +#define DEFENSEONLY(X) X +#else +#define DEFENSEONLY(X) +#endif + +/* +** The ALWAYS and NEVER macros surround boolean expressions which +** are intended to always be true or false, respectively. Such +** expressions could be omitted from the code completely. But they +** are included in a few cases in order to enhance the resilience +** of the H3 library to unexpected behavior - to make the code "self-healing" +** or "ductile" rather than being "brittle" and crashing at the first +** hint of unplanned behavior. +** +** In other words, ALWAYS and NEVER are added for defensive code. +** +** When doing coverage testing ALWAYS and NEVER are hard-coded to +** be true and false so that the unreachable code they specify will +** not be counted as untested code. +*/ +#if defined(H3_OMIT_AUXILIARY_SAFETY_CHECKS) +#define ALWAYS(X) (1) +#define NEVER(X) (0) +#elif !defined(NDEBUG) +#define ALWAYS(X) ((X) ? 1 : (assert(0), 0)) +#define NEVER(X) ((X) ? (assert(0), 1) : 0) +#else +#define ALWAYS(X) (X) +#define NEVER(X) (X) +#endif + +#endif diff --git a/src/h3lib/include/h3Index.h b/src/h3lib/include/h3Index.h new file mode 100644 index 0000000..6ced3c4 --- /dev/null +++ b/src/h3lib/include/h3Index.h @@ -0,0 +1,182 @@ +/* + * Copyright 2016-2018, 2020 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file h3Index.h + * @brief H3Index functions. + */ + +#ifndef H3INDEX_H +#define H3INDEX_H + +#include "faceijk.h" +#include "h3api.h" + +// define's of constants and macros for bitwise manipulation of H3Index's. + +/** The number of bits in an H3 index. */ +#define H3_NUM_BITS 64 + +/** The bit offset of the max resolution digit in an H3 index. */ +#define H3_MAX_OFFSET 63 + +/** The bit offset of the mode in an H3 index. */ +#define H3_MODE_OFFSET 59 + +/** The bit offset of the base cell in an H3 index. */ +#define H3_BC_OFFSET 45 + +/** The bit offset of the resolution in an H3 index. */ +#define H3_RES_OFFSET 52 + +/** The bit offset of the reserved bits in an H3 index. */ +#define H3_RESERVED_OFFSET 56 + +/** The number of bits in a single H3 resolution digit. */ +#define H3_PER_DIGIT_OFFSET 3 + +/** 1 in the highest bit, 0's everywhere else. */ +#define H3_HIGH_BIT_MASK ((uint64_t)(1) << H3_MAX_OFFSET) + +/** 0 in the highest bit, 1's everywhere else. */ +#define H3_HIGH_BIT_MASK_NEGATIVE (~H3_HIGH_BIT_MASK) + +/** 1's in the 4 mode bits, 0's everywhere else. */ +#define H3_MODE_MASK ((uint64_t)(15) << H3_MODE_OFFSET) + +/** 0's in the 4 mode bits, 1's everywhere else. */ +#define H3_MODE_MASK_NEGATIVE (~H3_MODE_MASK) + +/** 1's in the 7 base cell bits, 0's everywhere else. */ +#define H3_BC_MASK ((uint64_t)(127) << H3_BC_OFFSET) + +/** 0's in the 7 base cell bits, 1's everywhere else. */ +#define H3_BC_MASK_NEGATIVE (~H3_BC_MASK) + +/** 1's in the 4 resolution bits, 0's everywhere else. */ +#define H3_RES_MASK (UINT64_C(15) << H3_RES_OFFSET) + +/** 0's in the 4 resolution bits, 1's everywhere else. */ +#define H3_RES_MASK_NEGATIVE (~H3_RES_MASK) + +/** 1's in the 3 reserved bits, 0's everywhere else. */ +#define H3_RESERVED_MASK ((uint64_t)(7) << H3_RESERVED_OFFSET) + +/** 0's in the 3 reserved bits, 1's everywhere else. */ +#define H3_RESERVED_MASK_NEGATIVE (~H3_RESERVED_MASK) + +/** 1's in the 3 bits of res 15 digit bits, 0's everywhere else. */ +#define H3_DIGIT_MASK ((uint64_t)(7)) + +/** 0's in the 7 base cell bits, 1's everywhere else. */ +#define H3_DIGIT_MASK_NEGATIVE (~H3_DIGIT_MASK) + +/** + * H3 index with mode 0, res 0, base cell 0, and 7 for all index digits. + * Typically used to initialize the creation of an H3 cell index, which + * expects all direction digits to be 7 beyond the cell's resolution. + */ +#define H3_INIT (UINT64_C(35184372088831)) + +/** + * Gets the highest bit of the H3 index. + */ +#define H3_GET_HIGH_BIT(h3) ((int)((((h3)&H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))) + +/** + * Sets the highest bit of the h3 to v. + */ +#define H3_SET_HIGH_BIT(h3, v) \ + (h3) = (((h3)&H3_HIGH_BIT_MASK_NEGATIVE) | \ + (((uint64_t)(v)) << H3_MAX_OFFSET)) + +/** + * Gets the integer mode of h3. + */ +#define H3_GET_MODE(h3) ((int)((((h3)&H3_MODE_MASK) >> H3_MODE_OFFSET))) + +/** + * Sets the integer mode of h3 to v. + */ +#define H3_SET_MODE(h3, v) \ + (h3) = (((h3)&H3_MODE_MASK_NEGATIVE) | (((uint64_t)(v)) << H3_MODE_OFFSET)) + +/** + * Gets the integer base cell of h3. + */ +#define H3_GET_BASE_CELL(h3) ((int)((((h3)&H3_BC_MASK) >> H3_BC_OFFSET))) + +/** + * Sets the integer base cell of h3 to bc. + */ +#define H3_SET_BASE_CELL(h3, bc) \ + (h3) = (((h3)&H3_BC_MASK_NEGATIVE) | (((uint64_t)(bc)) << H3_BC_OFFSET)) + +/** + * Gets the integer resolution of h3. + */ +#define H3_GET_RESOLUTION(h3) ((int)((((h3)&H3_RES_MASK) >> H3_RES_OFFSET))) + +/** + * Sets the integer resolution of h3. + */ +#define H3_SET_RESOLUTION(h3, res) \ + (h3) = (((h3)&H3_RES_MASK_NEGATIVE) | (((uint64_t)(res)) << H3_RES_OFFSET)) + +/** + * Gets the resolution res integer digit (0-7) of h3. + */ +#define H3_GET_INDEX_DIGIT(h3, res) \ + ((Direction)((((h3) >> ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET)) & \ + H3_DIGIT_MASK))) + +/** + * Sets a value in the reserved space. Setting to non-zero may produce invalid + * indexes. + */ +#define H3_SET_RESERVED_BITS(h3, v) \ + (h3) = (((h3)&H3_RESERVED_MASK_NEGATIVE) | \ + (((uint64_t)(v)) << H3_RESERVED_OFFSET)) + +/** + * Gets a value in the reserved space. Should always be zero for valid indexes. + */ +#define H3_GET_RESERVED_BITS(h3) \ + ((int)((((h3)&H3_RESERVED_MASK) >> H3_RESERVED_OFFSET))) + +/** + * Sets the resolution res digit of h3 to the integer digit (0-7) + */ +#define H3_SET_INDEX_DIGIT(h3, res, digit) \ + (h3) = (((h3) & ~((H3_DIGIT_MASK \ + << ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET)))) | \ + (((uint64_t)(digit)) \ + << ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET))) + +void setH3Index(H3Index *h, int res, int baseCell, Direction initDigit); +int isResolutionClassIII(int r); + +// Internal functions + +int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk); +H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk); +H3Index _faceIjkToH3(const FaceIJK *fijk, int res); +Direction _h3LeadingNonZeroDigit(H3Index h); +H3Index _h3RotatePent60ccw(H3Index h); +H3Index _h3RotatePent60cw(H3Index h); +H3Index _h3Rotate60ccw(H3Index h); +H3Index _h3Rotate60cw(H3Index h); +DECLSPEC H3Index _zeroIndexDigits(H3Index h, int start, int end); + +#endif diff --git a/src/h3lib/include/h3api.h.in b/src/h3lib/include/h3api.h.in new file mode 100644 index 0000000..6d9be68 --- /dev/null +++ b/src/h3lib/include/h3api.h.in @@ -0,0 +1,768 @@ +/* + * Copyright 2016-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file h3api.h + * @brief Primary H3 core library entry points. + * + * This file defines the public API of the H3 library. Incompatible changes to + * these functions require the library's major version be increased. + */ + +#ifndef H3API_H +#define H3API_H + +/* + * Preprocessor code to support renaming (prefixing) the public API. + * All public functions should be wrapped in H3_EXPORT so they can be + * renamed. + */ +#ifdef H3_PREFIX +#define XTJOIN(a, b) a##b +#define TJOIN(a, b) XTJOIN(a, b) + +/* export joins the user provided prefix with our exported function name */ +#define H3_EXPORT(name) TJOIN(H3_PREFIX, name) +#else +#define H3_EXPORT(name) name +#endif + +/* Windows DLL requires attributes indicating what to export */ +#if _WIN32 && BUILD_SHARED_LIBS +#if BUILDING_H3 +#define DECLSPEC __declspec(dllexport) +#else +#define DECLSPEC __declspec(dllimport) +#endif +#else +#define DECLSPEC +#endif + +/* For uint64_t */ +#include +/* For size_t */ +#include + +/* + * H3 is compiled as C, not C++ code. `extern "C"` is needed for C++ code + * to be able to use the library. + */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Identifier for an object (cell, edge, etc) in the H3 system. + * + * The H3Index fits within a 64-bit unsigned integer. + */ +typedef uint64_t H3Index; + +/** + * Invalid index used to indicate an error from latLngToCell and related + * functions or missing data in arrays of H3 indices. Analogous to NaN in + * floating point. + */ +#define H3_NULL 0 + +/** @brief Result code (success or specific error) from an H3 operation */ +typedef uint32_t H3Error; + +typedef enum { + E_SUCCESS = 0, // Success (no error) + E_FAILED = + 1, // The operation failed but a more specific error is not available + E_DOMAIN = 2, // Argument was outside of acceptable range (when a more + // specific error code is not available) + E_LATLNG_DOMAIN = + 3, // Latitude or longitude arguments were outside of acceptable range + E_RES_DOMAIN = 4, // Resolution argument was outside of acceptable range + E_CELL_INVALID = 5, // `H3Index` cell argument was not valid + E_DIR_EDGE_INVALID = 6, // `H3Index` directed edge argument was not valid + E_UNDIR_EDGE_INVALID = + 7, // `H3Index` undirected edge argument was not valid + E_VERTEX_INVALID = 8, // `H3Index` vertex argument was not valid + E_PENTAGON = 9, // Pentagon distortion was encountered which the algorithm + // could not handle it + E_DUPLICATE_INPUT = 10, // Duplicate input was encountered in the arguments + // and the algorithm could not handle it + E_NOT_NEIGHBORS = 11, // `H3Index` cell arguments were not neighbors + E_RES_MISMATCH = + 12, // `H3Index` cell arguments had incompatible resolutions + E_MEMORY_ALLOC = 13, // Necessary memory allocation failed + E_MEMORY_BOUNDS = 14, // Bounds of provided memory were not large enough + E_OPTION_INVALID = 15 // Mode or flags argument was not valid. +} H3ErrorCodes; + +/* library version numbers generated from VERSION file */ +// clang-format off +#define H3_VERSION_MAJOR @H3_VERSION_MAJOR@ +#define H3_VERSION_MINOR @H3_VERSION_MINOR@ +#define H3_VERSION_PATCH @H3_VERSION_PATCH@ +// clang-format on + +/** Maximum number of cell boundary vertices; worst case is pentagon: + * 5 original verts + 5 edge crossings + */ +#define MAX_CELL_BNDRY_VERTS 10 + +/** @struct LatLng + @brief latitude/longitude in radians +*/ +typedef struct { + double lat; ///< latitude in radians + double lng; ///< longitude in radians +} LatLng; + +/** @struct CellBoundary + @brief cell boundary in latitude/longitude +*/ +typedef struct { + int numVerts; ///< number of vertices + LatLng verts[MAX_CELL_BNDRY_VERTS]; ///< vertices in ccw order +} CellBoundary; + +/** @struct GeoLoop + * @brief similar to CellBoundary, but requires more alloc work + */ +typedef struct { + int numVerts; + LatLng *verts; +} GeoLoop; + +/** @struct GeoPolygon + * @brief Simplified core of GeoJSON Polygon coordinates definition + */ +typedef struct { + GeoLoop geoloop; ///< exterior boundary of the polygon + int numHoles; ///< number of elements in the array pointed to by holes + GeoLoop *holes; ///< interior boundaries (holes) in the polygon +} GeoPolygon; + +/** @struct GeoMultiPolygon + * @brief Simplified core of GeoJSON MultiPolygon coordinates definition + */ +typedef struct { + int numPolygons; + GeoPolygon *polygons; +} GeoMultiPolygon; + +/** @struct LinkedLatLng + * @brief A coordinate node in a linked geo structure, part of a linked list + */ +typedef struct LinkedLatLng LinkedLatLng; +struct LinkedLatLng { + LatLng vertex; + LinkedLatLng *next; +}; + +/** @struct LinkedGeoLoop + * @brief A loop node in a linked geo structure, part of a linked list + */ +typedef struct LinkedGeoLoop LinkedGeoLoop; +struct LinkedGeoLoop { + LinkedLatLng *first; + LinkedLatLng *last; + LinkedGeoLoop *next; +}; + +/** @struct LinkedGeoPolygon + * @brief A polygon node in a linked geo structure, part of a linked list. + */ +typedef struct LinkedGeoPolygon LinkedGeoPolygon; +struct LinkedGeoPolygon { + LinkedGeoLoop *first; + LinkedGeoLoop *last; + LinkedGeoPolygon *next; +}; + +/** @struct CoordIJ + * @brief IJ hexagon coordinates + * + * Each axis is spaced 120 degrees apart. + */ +typedef struct { + int i; ///< i component + int j; ///< j component +} CoordIJ; + +/** @defgroup latLngToCell latLngToCell + * Functions for latLngToCell + * @{ + */ +/** @brief find the H3 index of the resolution res cell containing the lat/lng + */ +DECLSPEC H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, + H3Index *out); +/** @} */ + +/** @defgroup cellToLatLng cellToLatLng + * Functions for cellToLatLng + * @{ + */ +/** @brief find the lat/lng center point g of the cell h3 */ +DECLSPEC H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g); +/** @} */ + +/** @defgroup cellToBoundary cellToBoundary + * Functions for cellToBoundary + * @{ + */ +/** @brief give the cell boundary in lat/lng coordinates for the cell h3 */ +DECLSPEC H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *gp); +/** @} */ + +/** @defgroup gridDisk gridDisk + * Functions for gridDisk + * @{ + */ +/** @brief maximum number of hexagons in k-ring */ +DECLSPEC H3Error H3_EXPORT(maxGridDiskSize)(int k, int64_t *out); + +/** @brief hexagons neighbors in all directions, assuming no pentagons */ +DECLSPEC H3Error H3_EXPORT(gridDiskUnsafe)(H3Index origin, int k, H3Index *out); +/** @} */ + +/** @brief hexagons neighbors in all directions, assuming no pentagons, + * reporting distance from origin */ +DECLSPEC H3Error H3_EXPORT(gridDiskDistancesUnsafe)(H3Index origin, int k, + H3Index *out, + int *distances); + +/** @brief hexagons neighbors in all directions reporting distance from origin + */ +DECLSPEC H3Error H3_EXPORT(gridDiskDistancesSafe)(H3Index origin, int k, + H3Index *out, int *distances); + +/** @brief collection of hex rings sorted by ring for all given hexagons */ +DECLSPEC H3Error H3_EXPORT(gridDisksUnsafe)(H3Index *h3Set, int length, int k, + H3Index *out); + +/** @brief hexagon neighbors in all directions */ +DECLSPEC H3Error H3_EXPORT(gridDisk)(H3Index origin, int k, H3Index *out); +/** @} */ + +/** @defgroup gridDiskDistances gridDiskDistances + * Functions for gridDiskDistances + * @{ + */ +/** @brief hexagon neighbors in all directions, reporting distance from origin + */ +DECLSPEC H3Error H3_EXPORT(gridDiskDistances)(H3Index origin, int k, + H3Index *out, int *distances); +/** @} */ + +/** @defgroup gridRingUnsafe gridRingUnsafe + * Functions for gridRingUnsafe + * @{ + */ +/** @brief hollow hexagon ring at some origin */ +DECLSPEC H3Error H3_EXPORT(gridRingUnsafe)(H3Index origin, int k, H3Index *out); +/** @} */ + +/** @defgroup polygonToCells polygonToCells + * Functions for polygonToCells + * @{ + */ +/** @brief maximum number of hexagons that could be in the geoloop */ +DECLSPEC H3Error H3_EXPORT(maxPolygonToCellsSize)(const GeoPolygon *geoPolygon, + int res, uint32_t flags, + int64_t *out); + +/** @brief hexagons within the given geopolygon */ +DECLSPEC H3Error H3_EXPORT(polygonToCells)(const GeoPolygon *geoPolygon, + int res, uint32_t flags, + H3Index *out); +/** @} */ + +/** @defgroup cellsToMultiPolygon cellsToMultiPolygon + * Functions for cellsToMultiPolygon (currently a binding-only concept) + * @{ + */ +/** @brief Create a LinkedGeoPolygon from a set of contiguous hexagons */ +DECLSPEC H3Error H3_EXPORT(cellsToLinkedMultiPolygon)(const H3Index *h3Set, + const int numHexes, + LinkedGeoPolygon *out); + +/** @brief Free all memory created for a LinkedGeoPolygon */ +DECLSPEC void H3_EXPORT(destroyLinkedMultiPolygon)(LinkedGeoPolygon *polygon); +/** @} */ + +/** @defgroup degsToRads degsToRads + * Functions for degsToRads + * @{ + */ +/** @brief converts degrees to radians */ +DECLSPEC double H3_EXPORT(degsToRads)(double degrees); +/** @} */ + +/** @defgroup radsToDegs radsToDegs + * Functions for radsToDegs + * @{ + */ +/** @brief converts radians to degrees */ +DECLSPEC double H3_EXPORT(radsToDegs)(double radians); +/** @} */ + +/** @defgroup greatCircleDistance greatCircleDistance + * Functions for distance + * @{ + */ +/** @brief "great circle distance" between pairs of LatLng points in radians*/ +DECLSPEC double H3_EXPORT(greatCircleDistanceRads)(const LatLng *a, + const LatLng *b); + +/** @brief "great circle distance" between pairs of LatLng points in + * kilometers*/ +DECLSPEC double H3_EXPORT(greatCircleDistanceKm)(const LatLng *a, + const LatLng *b); + +/** @brief "great circle distance" between pairs of LatLng points in meters*/ +DECLSPEC double H3_EXPORT(greatCircleDistanceM)(const LatLng *a, + const LatLng *b); +/** @} */ + +/** @defgroup getHexagonAreaAvg getHexagonAreaAvg + * Functions for getHexagonAreaAvg + * @{ + */ +/** @brief average hexagon area in square kilometers (excludes pentagons) */ +DECLSPEC H3Error H3_EXPORT(getHexagonAreaAvgKm2)(int res, double *out); + +/** @brief average hexagon area in square meters (excludes pentagons) */ +DECLSPEC H3Error H3_EXPORT(getHexagonAreaAvgM2)(int res, double *out); +/** @} */ + +/** @defgroup cellArea cellArea + * Functions for cellArea + * @{ + */ +/** @brief exact area for a specific cell (hexagon or pentagon) in radians^2 */ +DECLSPEC H3Error H3_EXPORT(cellAreaRads2)(H3Index h, double *out); + +/** @brief exact area for a specific cell (hexagon or pentagon) in kilometers^2 + */ +DECLSPEC H3Error H3_EXPORT(cellAreaKm2)(H3Index h, double *out); + +/** @brief exact area for a specific cell (hexagon or pentagon) in meters^2 */ +DECLSPEC H3Error H3_EXPORT(cellAreaM2)(H3Index h, double *out); +/** @} */ + +/** @defgroup getHexagonEdgeLengthAvg getHexagonEdgeLengthAvg + * Functions for getHexagonEdgeLengthAvg + * @{ + */ +/** @brief average hexagon edge length in kilometers (excludes pentagons) */ +DECLSPEC H3Error H3_EXPORT(getHexagonEdgeLengthAvgKm)(int res, double *out); + +/** @brief average hexagon edge length in meters (excludes pentagons) */ +DECLSPEC H3Error H3_EXPORT(getHexagonEdgeLengthAvgM)(int res, double *out); +/** @} */ + +/** @defgroup edgeLength edgeLength + * Functions for edgeLength + * @{ + */ +/** @brief exact length for a specific directed edge in radians*/ +DECLSPEC H3Error H3_EXPORT(edgeLengthRads)(H3Index edge, double *length); + +/** @brief exact length for a specific directed edge in kilometers*/ +DECLSPEC H3Error H3_EXPORT(edgeLengthKm)(H3Index edge, double *length); + +/** @brief exact length for a specific directed edge in meters*/ +DECLSPEC H3Error H3_EXPORT(edgeLengthM)(H3Index edge, double *length); +/** @} */ + +/** @defgroup getNumCells getNumCells + * Functions for getNumCells + * @{ + */ +/** @brief number of cells (hexagons and pentagons) for a given resolution + * + * It works out to be `2 + 120*7^r` for resolution `r`. + * + * # Mathematical notes + * + * Let h(n) be the number of children n levels below + * a single *hexagon*. + * + * Then h(n) = 7^n. + * + * Let p(n) be the number of children n levels below + * a single *pentagon*. + * + * Then p(0) = 1, and p(1) = 6, since each pentagon + * has 5 hexagonal immediate children and 1 pentagonal + * immediate child. + * + * In general, we have the recurrence relation + * + * p(n) = 5*h(n-1) + p(n-1) + * = 5*7^(n-1) + p(n-1). + * + * Working through the recurrence, we get that + * + * p(n) = 1 + 5*\sum_{k=1}^n 7^{k-1} + * = 1 + 5*(7^n - 1)/6, + * + * using the closed form for a geometric series. + * + * Using the closed forms for h(n) and p(n), we can + * get a closed form for the total number of cells + * at resolution r: + * + * c(r) = 12*p(r) + 110*h(r) + * = 2 + 120*7^r. + * + * + * @param res H3 cell resolution + * + * @return number of cells at resolution `res` + */ +DECLSPEC H3Error H3_EXPORT(getNumCells)(int res, int64_t *out); +/** @} */ + +/** @defgroup getRes0Cells getRes0Cells + * Functions for getRes0Cells + * @{ + */ +/** @brief returns the number of resolution 0 cells (hexagons and pentagons) */ +DECLSPEC int H3_EXPORT(res0CellCount)(); + +/** @brief provides all base cells in H3Index format*/ +DECLSPEC H3Error H3_EXPORT(getRes0Cells)(H3Index *out); +/** @} */ + +/** @defgroup getPentagons getPentagons + * Functions for getPentagons + * @{ + */ +/** @brief returns the number of pentagons per resolution */ +DECLSPEC int H3_EXPORT(pentagonCount)(); + +/** @brief generates all pentagons at the specified resolution */ +DECLSPEC H3Error H3_EXPORT(getPentagons)(int res, H3Index *out); +/** @} */ + +/** @defgroup getResolution getResolution + * Functions for getResolution + * @{ + */ +/** @brief returns the resolution of the provided H3 index + * Works on both cells and directed edges. */ +DECLSPEC int H3_EXPORT(getResolution)(H3Index h); +/** @} */ + +/** @defgroup getBaseCellNumber getBaseCellNumber + * Functions for getBaseCellNumber + * @{ + */ +/** @brief returns the base cell "number" (0 to 121) of the provided H3 cell + * + * Note: Technically works on H3 edges, but will return base cell of the + * origin cell. */ +DECLSPEC int H3_EXPORT(getBaseCellNumber)(H3Index h); +/** @} */ + +/** @defgroup stringToH3 stringToH3 + * Functions for stringToH3 + * @{ + */ +/** @brief converts the canonical string format to H3Index format */ +DECLSPEC H3Error H3_EXPORT(stringToH3)(const char *str, H3Index *out); +/** @} */ + +/** @defgroup h3ToString h3ToString + * Functions for h3ToString + * @{ + */ +/** @brief converts an H3Index to a canonical string */ +DECLSPEC H3Error H3_EXPORT(h3ToString)(H3Index h, char *str, size_t sz); +/** @} */ + +/** @defgroup isValidCell isValidCell + * Functions for isValidCell + * @{ + */ +/** @brief confirms if an H3Index is a valid cell (hexagon or pentagon) + * In particular, returns 0 (False) for H3 directed edges or invalid data + */ +DECLSPEC int H3_EXPORT(isValidCell)(H3Index h); +/** @} */ + +/** @defgroup cellToParent cellToParent + * Functions for cellToParent + * @{ + */ +/** @brief returns the parent (or grandparent, etc) cell of the given cell + */ +DECLSPEC H3Error H3_EXPORT(cellToParent)(H3Index h, int parentRes, + H3Index *parent); +/** @} */ + +/** @defgroup cellToChildren cellToChildren + * Functions for cellToChildren + * @{ + */ +/** @brief determines the exact number of children (or grandchildren, etc) + * that would be returned for the given cell */ +DECLSPEC H3Error H3_EXPORT(cellToChildrenSize)(H3Index h, int childRes, + int64_t *out); + +/** @brief provides the children (or grandchildren, etc) of the given cell */ +DECLSPEC H3Error H3_EXPORT(cellToChildren)(H3Index h, int childRes, + H3Index *children); +/** @} */ + +/** @defgroup cellToCenterChild cellToCenterChild + * Functions for cellToCenterChild + * @{ + */ +/** @brief returns the center child of the given cell at the specified + * resolution */ +DECLSPEC H3Error H3_EXPORT(cellToCenterChild)(H3Index h, int childRes, + H3Index *child); +/** @} */ + +/** @defgroup cellToChildPos cellToChildPos + * Functions for cellToChildPos + * @{ + */ +/** @brief Returns the position of the cell within an ordered list of all + * children of the cell's parent at the specified resolution */ +DECLSPEC H3Error H3_EXPORT(cellToChildPos)(H3Index child, int parentRes, + int64_t *out); +/** @} */ + +/** @defgroup childPosToCell childPosToCell + * Functions for childPosToCell + * @{ + */ +/** @brief Returns the child cell at a given position within an ordered list of + * all children at the specified resolution */ +DECLSPEC H3Error H3_EXPORT(childPosToCell)(int64_t childPos, H3Index parent, + int childRes, H3Index *child); +/** @} */ + +/** @defgroup compactCells compactCells + * Functions for compactCells + * @{ + */ +/** @brief compacts the given set of hexagons as best as possible */ +DECLSPEC H3Error H3_EXPORT(compactCells)(const H3Index *h3Set, + H3Index *compactedSet, + const int64_t numHexes); +/** @} */ + +/** @defgroup uncompactCells uncompactCells + * Functions for uncompactCells + * @{ + */ +/** @brief determines the exact number of hexagons that will be uncompacted + * from the compacted set */ +DECLSPEC H3Error H3_EXPORT(uncompactCellsSize)(const H3Index *compactedSet, + const int64_t numCompacted, + const int res, int64_t *out); + +/** @brief uncompacts the compacted hexagon set */ +DECLSPEC H3Error H3_EXPORT(uncompactCells)(const H3Index *compactedSet, + const int64_t numCompacted, + H3Index *outSet, + const int64_t numOut, const int res); +/** @} */ + +/** @defgroup isResClassIII isResClassIII + * Functions for isResClassIII + * @{ + */ +/** @brief determines if a hexagon is Class III (or Class II) */ +DECLSPEC int H3_EXPORT(isResClassIII)(H3Index h); +/** @} */ + +/** @defgroup isPentagon isPentagon + * Functions for isPentagon + * @{ + */ +/** @brief determines if an H3 cell is a pentagon */ +DECLSPEC int H3_EXPORT(isPentagon)(H3Index h); +/** @} */ + +/** @defgroup getIcosahedronFaces getIcosahedronFaces + * Functions for getIcosahedronFaces + * @{ + */ +/** @brief Max number of icosahedron faces intersected by an index */ +DECLSPEC H3Error H3_EXPORT(maxFaceCount)(H3Index h3, int *out); + +/** @brief Find all icosahedron faces intersected by a given H3 index */ +DECLSPEC H3Error H3_EXPORT(getIcosahedronFaces)(H3Index h3, int *out); +/** @} */ + +/** @defgroup areNeighborCells areNeighborCells + * Functions for areNeighborCells + * @{ + */ +/** @brief returns whether or not the provided hexagons border */ +DECLSPEC H3Error H3_EXPORT(areNeighborCells)(H3Index origin, + H3Index destination, int *out); +/** @} */ + +/** @defgroup cellsToDirectedEdge cellsToDirectedEdge + * Functions for cellsToDirectedEdge + * @{ + */ +/** @brief returns the directed edge H3Index for the specified origin and + * destination */ +DECLSPEC H3Error H3_EXPORT(cellsToDirectedEdge)(H3Index origin, + H3Index destination, + H3Index *out); +/** @} */ + +/** @defgroup isValidDirectedEdge isValidDirectedEdge + * Functions for isValidDirectedEdge + * @{ + */ +/** @brief returns whether the H3Index is a valid directed edge */ +DECLSPEC int H3_EXPORT(isValidDirectedEdge)(H3Index edge); +/** @} */ + +/** @defgroup getDirectedEdgeOrigin \ + * getDirectedEdgeOrigin + * Functions for getDirectedEdgeOrigin + * @{ + */ +/** @brief Returns the origin hexagon H3Index from the directed edge + * H3Index */ +DECLSPEC H3Error H3_EXPORT(getDirectedEdgeOrigin)(H3Index edge, H3Index *out); +/** @} */ + +/** @defgroup getDirectedEdgeDestination \ + * getDirectedEdgeDestination + * Functions for getDirectedEdgeDestination + * @{ + */ +/** @brief Returns the destination hexagon H3Index from the directed edge + * H3Index */ +DECLSPEC H3Error H3_EXPORT(getDirectedEdgeDestination)(H3Index edge, + H3Index *out); +/** @} */ + +/** @defgroup directedEdgeToCells \ + * directedEdgeToCells + * Functions for directedEdgeToCells + * @{ + */ +/** @brief Returns the origin and destination hexagons from the directed + * edge H3Index */ +DECLSPEC H3Error H3_EXPORT(directedEdgeToCells)(H3Index edge, + H3Index *originDestination); +/** @} */ + +/** @defgroup originToDirectedEdges \ + * originToDirectedEdges + * Functions for originToDirectedEdges + * @{ + */ +/** @brief Returns the 6 (or 5 for pentagons) edges associated with the H3Index + */ +DECLSPEC H3Error H3_EXPORT(originToDirectedEdges)(H3Index origin, + H3Index *edges); +/** @} */ + +/** @defgroup directedEdgeToBoundary directedEdgeToBoundary + * Functions for directedEdgeToBoundary + * @{ + */ +/** @brief Returns the CellBoundary containing the coordinates of the edge */ +DECLSPEC H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, + CellBoundary *gb); +/** @} */ + +/** @defgroup cellToVertex cellToVertex + * Functions for cellToVertex + * @{ + */ +/** @brief Returns a single vertex for a given cell, as an H3 index */ +DECLSPEC H3Error H3_EXPORT(cellToVertex)(H3Index origin, int vertexNum, + H3Index *out); +/** @} */ + +/** @defgroup cellToVertexes cellToVertexes + * Functions for cellToVertexes + * @{ + */ +/** @brief Returns all vertexes for a given cell, as H3 indexes */ +DECLSPEC H3Error H3_EXPORT(cellToVertexes)(H3Index origin, H3Index *vertexes); +/** @} */ + +/** @defgroup vertexToLatLng vertexToLatLng + * Functions for vertexToLatLng + * @{ + */ +/** @brief Returns a single vertex for a given cell, as an H3 index */ +DECLSPEC H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *point); +/** @} */ + +/** @defgroup isValidVertex isValidVertex + * Functions for isValidVertex + * @{ + */ +/** @brief Whether the input is a valid H3 vertex */ +DECLSPEC int H3_EXPORT(isValidVertex)(H3Index vertex); +/** @} */ + +/** @defgroup gridDistance gridDistance + * Functions for gridDistance + * @{ + */ +/** @brief Returns grid distance between two indexes */ +DECLSPEC H3Error H3_EXPORT(gridDistance)(H3Index origin, H3Index h3, + int64_t *distance); +/** @} */ + +/** @defgroup gridPathCells gridPathCells + * Functions for gridPathCells + * @{ + */ +/** @brief Number of indexes in a line connecting two indexes */ +DECLSPEC H3Error H3_EXPORT(gridPathCellsSize)(H3Index start, H3Index end, + int64_t *size); + +/** @brief Line of h3 indexes connecting two indexes */ +DECLSPEC H3Error H3_EXPORT(gridPathCells)(H3Index start, H3Index end, + H3Index *out); +/** @} */ + +/** @defgroup cellToLocalIj cellToLocalIj + * Functions for cellToLocalIj + * @{ + */ +/** @brief Returns two dimensional coordinates for the given index */ +DECLSPEC H3Error H3_EXPORT(cellToLocalIj)(H3Index origin, H3Index h3, + uint32_t mode, CoordIJ *out); +/** @} */ + +/** @defgroup localIjToCell localIjToCell + * Functions for localIjToCell + * @{ + */ +/** @brief Returns index for the given two dimensional coordinates */ +DECLSPEC H3Error H3_EXPORT(localIjToCell)(H3Index origin, const CoordIJ *ij, + uint32_t mode, H3Index *out); +/** @} */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/h3lib/include/iterators.h b/src/h3lib/include/iterators.h new file mode 100644 index 0000000..ad735ed --- /dev/null +++ b/src/h3lib/include/iterators.h @@ -0,0 +1,86 @@ +/* + * Copyright 2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @file iterators.h + * @brief Iterator structs and functions for the children of a cell, + * or cells at a given resolution. + */ + +#ifndef ITERATORS_H +#define ITERATORS_H + +#include + +#include "h3api.h" + +/** + * IterCellsChildren: struct for iterating through the descendants of + * a given cell. + * + * Constructors: + * + * Initialize with either `iterInitParent` or `iterInitBaseCellNum`. + * `iterInitParent` sets up an iterator for all the children of a given + * parent cell at a given resolution. + * + * `iterInitBaseCellNum` sets up an iterator for children cells, given + * a base cell number (0--121). + * + * Iteration: + * + * Step iterator with `iterStepChild`. + * During the lifetime of the `IterCellsChildren`, the current iterate + * is accessed via the `IterCellsChildren.h` member. + * When the iterator is exhausted or if there was an error in initialization, + * `IterCellsChildren.h` will be `H3_NULL` even after calling `iterStepChild`. + */ +typedef struct { + H3Index h; + int _parentRes; // parent resolution + int _skipDigit; // this digit skips `1` for pentagons +} IterCellsChildren; + +DECLSPEC IterCellsChildren iterInitParent(H3Index h, int childRes); +DECLSPEC IterCellsChildren iterInitBaseCellNum(int baseCellNum, int childRes); +DECLSPEC void iterStepChild(IterCellsChildren *iter); + +/** + * IterCellsResolution: struct for iterating through all cells at a given + * resolution + * + * Constructor: + * + * Initialize with `IterCellsResolution`. + * + * Iteration: + * + * Step iterator with `iterStepRes`. + * During the lifetime of the iterator the current iterate + * is accessed via the `IterCellsResolution.h` member. + * When the iterator is exhausted or if there was an error in initialization, + * `IterCellsResolution.h` will be `H3_NULL` even after calling `iterStepRes`. + */ +typedef struct { + H3Index h; + int _baseCellNum; + int _res; + IterCellsChildren _itC; +} IterCellsResolution; + +DECLSPEC IterCellsResolution iterInitRes(int res); +DECLSPEC void iterStepRes(IterCellsResolution *iter); + +#endif diff --git a/src/h3lib/include/latLng.h b/src/h3lib/include/latLng.h new file mode 100644 index 0000000..2dc674e --- /dev/null +++ b/src/h3lib/include/latLng.h @@ -0,0 +1,51 @@ +/* + * Copyright 2016-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file latLng.h + * @brief Geodetic (lat/lng) functions. + */ + +#ifndef GEOPOINT_H +#define GEOPOINT_H + +#include +#include +#include + +#include "constants.h" +#include "h3api.h" + +/** epsilon of ~0.1mm in degrees */ +#define EPSILON_DEG .000000001 +/** epsilon of ~0.1mm in radians */ +#define EPSILON_RAD (EPSILON_DEG * M_PI_180) + +void setGeoDegs(LatLng *p, double latDegs, double lngDegs); +double constrainLat(double lat); +double constrainLng(double lng); + +bool geoAlmostEqual(const LatLng *p1, const LatLng *p2); +bool geoAlmostEqualThreshold(const LatLng *p1, const LatLng *p2, + double threshold); + +// Internal functions + +double _posAngleRads(double rads); +void _setGeoRads(LatLng *p, double latRads, double lngRads); +double _geoAzimuthRads(const LatLng *p1, const LatLng *p2); +void _geoAzDistanceRads(const LatLng *p1, double az, double distance, + LatLng *p2); + +#endif diff --git a/src/h3lib/include/linkedGeo.h b/src/h3lib/include/linkedGeo.h new file mode 100644 index 0000000..1abc341 --- /dev/null +++ b/src/h3lib/include/linkedGeo.h @@ -0,0 +1,88 @@ +/* + * Copyright 2017-2018, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file linkedGeo.h + * @brief Linked data structure for geo data + */ + +#ifndef LINKED_GEO_H +#define LINKED_GEO_H + +#include + +#include "bbox.h" +#include "h3api.h" +#include "latLng.h" + +// Macros for use with polygonAlgos.h +/** Macro: Init iteration vars for LinkedGeoLoop */ +#define INIT_ITERATION_LINKED_LOOP \ + LinkedLatLng *currentCoord = NULL; \ + LinkedLatLng *nextCoord = NULL + +/** Macro: Get the next coord in a linked loop, wrapping if needed */ +#define GET_NEXT_COORD(loop, coordToCheck) \ + coordToCheck == NULL ? loop->first : currentCoord->next + +/** Macro: Increment LinkedGeoLoop iteration, or break if done. */ +#define ITERATE_LINKED_LOOP(loop, vertexA, vertexB) \ + currentCoord = GET_NEXT_COORD(loop, currentCoord); \ + if (currentCoord == NULL) break; \ + vertexA = currentCoord->vertex; \ + nextCoord = GET_NEXT_COORD(loop, currentCoord->next); \ + vertexB = nextCoord->vertex + +/** Macro: Whether a LinkedGeoLoop is empty */ +#define IS_EMPTY_LINKED_LOOP(loop) loop->first == NULL + +H3Error normalizeMultiPolygon(LinkedGeoPolygon *root); +LinkedGeoPolygon *addNewLinkedPolygon(LinkedGeoPolygon *polygon); +LinkedGeoLoop *addNewLinkedLoop(LinkedGeoPolygon *polygon); +LinkedGeoLoop *addLinkedLoop(LinkedGeoPolygon *polygon, LinkedGeoLoop *loop); +LinkedLatLng *addLinkedCoord(LinkedGeoLoop *loop, const LatLng *vertex); +int countLinkedPolygons(LinkedGeoPolygon *polygon); +int countLinkedLoops(LinkedGeoPolygon *polygon); +int countLinkedCoords(LinkedGeoLoop *loop); +void destroyLinkedGeoLoop(LinkedGeoLoop *loop); + +// The following functions are created via macro in polygonAlgos.h, +// so their signatures are documented here: + +/** + * Create a bounding box from a LinkedGeoLoop + * @param geoloop Input GeoLoop + * @param bbox Output bbox + */ +void bboxFromLinkedGeoLoop(const LinkedGeoLoop *loop, BBox *bbox); + +/** + * Take a given LinkedGeoLoop data structure and check if it + * contains a given geo coordinate. + * @param loop The linked loop + * @param bbox The bbox for the loop + * @param coord The coordinate to check + * @return Whether the point is contained + */ +bool pointInsideLinkedGeoLoop(const LinkedGeoLoop *loop, const BBox *bbox, + const LatLng *coord); + +/** + * Whether the winding order of a given LinkedGeoLoop is clockwise + * @param loop The loop to check + * @return Whether the loop is clockwise + */ +bool isClockwiseLinkedGeoLoop(const LinkedGeoLoop *loop); + +#endif diff --git a/src/h3lib/include/localij.h b/src/h3lib/include/localij.h new file mode 100644 index 0000000..3ffa0e6 --- /dev/null +++ b/src/h3lib/include/localij.h @@ -0,0 +1,29 @@ +/* + * Copyright 2018 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file localij.h + * @brief Local IJ coordinate space functions. + */ + +#ifndef LOCALIJ_H +#define LOCALIJ_H + +#include "coordijk.h" +#include "h3api.h" + +H3Error cellToLocalIjk(H3Index origin, H3Index h3, CoordIJK *out); +H3Error localIjkToCell(H3Index origin, const CoordIJK *ijk, H3Index *out); + +#endif diff --git a/src/h3lib/include/mathExtensions.h b/src/h3lib/include/mathExtensions.h new file mode 100644 index 0000000..f7dfb68 --- /dev/null +++ b/src/h3lib/include/mathExtensions.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017-2018, 2022 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file mathExtensions.h + * @brief Math functions that should've been in math.h but aren't + */ + +#ifndef MATHEXTENSIONS_H +#define MATHEXTENSIONS_H + +#include + +/** + * MAX returns the maximum of two values. + */ +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +/** Evaluates to true if a + b would overflow for int32 */ +#define ADD_INT32S_OVERFLOWS(a, b) \ + ((a) > 0 ? (INT32_MAX - (a) < (b)) : (INT32_MIN - (a) > (b))) + +/** Evaluates to true if a - b would overflow for int32 */ +#define SUB_INT32S_OVERFLOWS(a, b) \ + ((a) >= 0 ? (INT32_MIN + (a) >= (b)) : (INT32_MAX + (a) + 1 < (b))) + +// Internal functions +int64_t _ipow(int64_t base, int64_t exp); + +#endif diff --git a/src/h3lib/include/polygon.h b/src/h3lib/include/polygon.h new file mode 100644 index 0000000..3641f9f --- /dev/null +++ b/src/h3lib/include/polygon.h @@ -0,0 +1,76 @@ +/* + * Copyright 2018, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file polygon.h + * @brief Polygon algorithms + */ + +#ifndef POLYGON_H +#define POLYGON_H + +#include + +#include "bbox.h" +#include "h3api.h" +#include "latLng.h" +#include "linkedGeo.h" + +// Macros for use with polygonAlgos.h +/** Macro: Init iteration vars for GeoLoop */ +#define INIT_ITERATION_GEOFENCE int loopIndex = -1 + +/** Macro: Increment GeoLoop loop iteration, or break if done. */ +#define ITERATE_GEOFENCE(geoloop, vertexA, vertexB) \ + if (++loopIndex >= geoloop->numVerts) break; \ + vertexA = geoloop->verts[loopIndex]; \ + vertexB = geoloop->verts[(loopIndex + 1) % geoloop->numVerts] + +/** Macro: Whether a GeoLoop is empty */ +#define IS_EMPTY_GEOFENCE(geoloop) geoloop->numVerts == 0 + +// Defined directly in polygon.c: +void bboxesFromGeoPolygon(const GeoPolygon *polygon, BBox *bboxes); +bool pointInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, + const LatLng *coord); + +// The following functions are created via macro in polygonAlgos.h, +// so their signatures are documented here: + +/** + * Create a bounding box from a GeoLoop + * @param geoloop Input GeoLoop + * @param bbox Output bbox + */ +void bboxFromGeoLoop(const GeoLoop *loop, BBox *bbox); + +/** + * Take a given GeoLoop data structure and check if it + * contains a given geo coordinate. + * @param loop The geoloop + * @param bbox The bbox for the loop + * @param coord The coordinate to check + * @return Whether the point is contained + */ +bool pointInsideGeoLoop(const GeoLoop *loop, const BBox *bbox, + const LatLng *coord); + +/** + * Whether the winding order of a given GeoLoop is clockwise + * @param loop The loop to check + * @return Whether the loop is clockwise + */ +bool isClockwiseGeoLoop(const GeoLoop *geoloop); + +#endif diff --git a/src/h3lib/include/polygonAlgos.h b/src/h3lib/include/polygonAlgos.h new file mode 100644 index 0000000..e6ce984 --- /dev/null +++ b/src/h3lib/include/polygonAlgos.h @@ -0,0 +1,235 @@ +/* + * Copyright 2018, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file + * @brief Include file for poylgon algorithms. This includes the core + * logic for algorithms acting over loops of coordinates, + * allowing them to be reused for both GeoLoop and + * LinkegGeoLoop structures. This file is intended to be + * included inline in a file that defines the type-specific + * macros required for iteration. + */ + +#include +#include +#include + +#include "bbox.h" +#include "constants.h" +#include "h3api.h" +#include "latLng.h" +#include "linkedGeo.h" +#include "polygon.h" + +#ifndef TYPE +#error "TYPE must be defined before including this header" +#endif + +#ifndef IS_EMPTY +#error "IS_EMPTY must be defined before including this header" +#endif + +#ifndef INIT_ITERATION +#error "INIT_ITERATION must be defined before including this header" +#endif + +#ifndef ITERATE +#error "ITERATE must be defined before including this header" +#endif + +#define LOOP_ALGO_XTJOIN(a, b) a##b +#define LOOP_ALGO_TJOIN(a, b) LOOP_ALGO_XTJOIN(a, b) +#define GENERIC_LOOP_ALGO(func) LOOP_ALGO_TJOIN(func, TYPE) + +/** Macro: Normalize longitude, dealing with transmeridian arcs */ +#define NORMALIZE_LNG(lng, isTransmeridian) \ + (isTransmeridian && lng < 0 ? lng + (double)M_2PI : lng) + +/** + * pointInside is the core loop of the point-in-poly algorithm + * @param loop The loop to check + * @param bbox The bbox for the loop being tested + * @param coord The coordinate to check + * @return Whether the point is contained + */ +bool GENERIC_LOOP_ALGO(pointInside)(const TYPE *loop, const BBox *bbox, + const LatLng *coord) { + // fail fast if we're outside the bounding box + if (!bboxContains(bbox, coord)) { + return false; + } + bool isTransmeridian = bboxIsTransmeridian(bbox); + bool contains = false; + + double lat = coord->lat; + double lng = NORMALIZE_LNG(coord->lng, isTransmeridian); + + LatLng a; + LatLng b; + + INIT_ITERATION; + + while (true) { + ITERATE(loop, a, b); + + // Ray casting algo requires the second point to always be higher + // than the first, so swap if needed + if (a.lat > b.lat) { + LatLng tmp = a; + a = b; + b = tmp; + } + + // If the latitude matches exactly, we'll hit an edge case where + // the ray passes through the vertex twice on successive segment + // checks. To avoid this, adjust the latiude northward if needed. + // + // NOTE: This currently means that a point at the north pole cannot + // be contained in any polygon. This is acceptable in current usage, + // because the point we test in this function at present is always + // a cell center or vertex, and no cell has a center or vertex on the + // north pole. If we need to expand this algo to more generic uses we + // might need to handle this edge case. + if (lat == a.lat || lat == b.lat) { + lat += DBL_EPSILON; + } + + // If we're totally above or below the latitude ranges, the test + // ray cannot intersect the line segment, so let's move on + if (lat < a.lat || lat > b.lat) { + continue; + } + + double aLng = NORMALIZE_LNG(a.lng, isTransmeridian); + double bLng = NORMALIZE_LNG(b.lng, isTransmeridian); + + // Rays are cast in the longitudinal direction, in case a point + // exactly matches, to decide tiebreakers, bias westerly + if (aLng == lng || bLng == lng) { + lng -= DBL_EPSILON; + } + + // For the latitude of the point, compute the longitude of the + // point that lies on the line segment defined by a and b + // This is done by computing the percent above a the lat is, + // and traversing the same percent in the longitudinal direction + // of a to b + double ratio = (lat - a.lat) / (b.lat - a.lat); + double testLng = + NORMALIZE_LNG(aLng + (bLng - aLng) * ratio, isTransmeridian); + + // Intersection of the ray + if (testLng > lng) { + contains = !contains; + } + } + + return contains; +} + +/** + * Create a bounding box from a simple polygon loop. + * Known limitations: + * - Does not support polygons with two adjacent points > 180 degrees of + * longitude apart. These will be interpreted as crossing the antimeridian. + * - Does not currently support polygons containing a pole. + * @param loop Loop of coordinates + * @param bbox Output bbox + */ +void GENERIC_LOOP_ALGO(bboxFrom)(const TYPE *loop, BBox *bbox) { + // Early exit if there are no vertices + if (IS_EMPTY(loop)) { + *bbox = (BBox){0}; + return; + } + + bbox->south = DBL_MAX; + bbox->west = DBL_MAX; + bbox->north = -DBL_MAX; + bbox->east = -DBL_MAX; + double minPosLng = DBL_MAX; + double maxNegLng = -DBL_MAX; + bool isTransmeridian = false; + + double lat; + double lng; + LatLng coord; + LatLng next; + + INIT_ITERATION; + + while (true) { + ITERATE(loop, coord, next); + + lat = coord.lat; + lng = coord.lng; + if (lat < bbox->south) bbox->south = lat; + if (lng < bbox->west) bbox->west = lng; + if (lat > bbox->north) bbox->north = lat; + if (lng > bbox->east) bbox->east = lng; + // Save the min positive and max negative longitude for + // use in the transmeridian case + if (lng > 0 && lng < minPosLng) minPosLng = lng; + if (lng < 0 && lng > maxNegLng) maxNegLng = lng; + // check for arcs > 180 degrees longitude, flagging as transmeridian + if (fabs(lng - next.lng) > M_PI) { + isTransmeridian = true; + } + } + // Swap east and west if transmeridian + if (isTransmeridian) { + bbox->east = maxNegLng; + bbox->west = minPosLng; + } +} + +/** + * Whether the winding order of a given loop is clockwise, with normalization + * for loops crossing the antimeridian. + * @param loop The loop to check + * @param isTransmeridian Whether the loop crosses the antimeridian + * @return Whether the loop is clockwise + */ +static bool GENERIC_LOOP_ALGO(isClockwiseNormalized)(const TYPE *loop, + bool isTransmeridian) { + double sum = 0; + LatLng a; + LatLng b; + + INIT_ITERATION; + while (true) { + ITERATE(loop, a, b); + // If we identify a transmeridian arc (> 180 degrees longitude), + // start over with the transmeridian flag set + if (!isTransmeridian && fabs(a.lng - b.lng) > M_PI) { + return GENERIC_LOOP_ALGO(isClockwiseNormalized)(loop, true); + } + sum += ((NORMALIZE_LNG(b.lng, isTransmeridian) - + NORMALIZE_LNG(a.lng, isTransmeridian)) * + (b.lat + a.lat)); + } + + return sum > 0; +} + +/** + * Whether the winding order of a given loop is clockwise. In GeoJSON, + * clockwise loops are always inner loops (holes). + * @param loop The loop to check + * @return Whether the loop is clockwise + */ +bool GENERIC_LOOP_ALGO(isClockwise)(const TYPE *loop) { + return GENERIC_LOOP_ALGO(isClockwiseNormalized)(loop, false); +} diff --git a/src/h3lib/include/vec2d.h b/src/h3lib/include/vec2d.h new file mode 100644 index 0000000..8ba0e9b --- /dev/null +++ b/src/h3lib/include/vec2d.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016-2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file vec2d.h + * @brief 2D floating point vector functions. + */ + +#ifndef VEC2D_H +#define VEC2D_H + +#include + +/** @struct Vec2d + * @brief 2D floating-point vector + */ +typedef struct { + double x; ///< x component + double y; ///< y component +} Vec2d; + +// Internal functions + +double _v2dMag(const Vec2d *v); +void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, + const Vec2d *p3, Vec2d *inter); +bool _v2dAlmostEquals(const Vec2d *p0, const Vec2d *p1); + +#endif diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3d.h new file mode 100644 index 0000000..0bd9ac5 --- /dev/null +++ b/src/h3lib/include/vec3d.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file vec3d.h + * @brief 3D floating point vector functions. + */ + +#ifndef VEC3D_H +#define VEC3D_H + +#include "latLng.h" + +/** @struct Vec3D + * @brief 3D floating point structure + */ +typedef struct { + double x; ///< x component + double y; ///< y component + double z; ///< z component +} Vec3d; + +void _geoToVec3d(const LatLng *geo, Vec3d *point); +double _pointSquareDist(const Vec3d *p1, const Vec3d *p2); + +#endif diff --git a/src/h3lib/include/vertex.h b/src/h3lib/include/vertex.h new file mode 100644 index 0000000..07f89d4 --- /dev/null +++ b/src/h3lib/include/vertex.h @@ -0,0 +1,44 @@ +/* + * Copyright 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file vertex.h + * @brief Functions for working with cell vertexes. + */ + +#ifndef H3VERTEX_H +#define H3VERTEX_H + +#include "h3Index.h" +#include "faceijk.h" + +/** @struct PentagonDirectionFaces + * @brief The faces in each axial direction of a given pentagon base cell + */ +typedef struct { + int baseCell; ///< base cell number + int faces[NUM_PENT_VERTS]; ///< face numbers for each axial direction, + /// in order, starting with J +} PentagonDirectionFaces; + +/** Invalid vertex number */ +#define INVALID_VERTEX_NUM -1 + +/** Max number of faces a base cell's descendants may appear on */ +#define MAX_BASE_CELL_FACES 5 + +int vertexNumForDirection(const H3Index origin, const Direction direction); +Direction directionForVertexNum(const H3Index origin, const int vertexNum); + +#endif diff --git a/src/h3lib/include/vertexGraph.h b/src/h3lib/include/vertexGraph.h new file mode 100644 index 0000000..5d17f59 --- /dev/null +++ b/src/h3lib/include/vertexGraph.h @@ -0,0 +1,69 @@ +/* + * Copyright 2017, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file vertexGraph.h + * @brief Data structure for storing a graph of vertices + */ + +#ifndef VERTEX_GRAPH_H +#define VERTEX_GRAPH_H + +#include +#include + +#include "latLng.h" + +/** @struct VertexNode + * @brief A single node in a vertex graph, part of a linked list + */ +typedef struct VertexNode VertexNode; +struct VertexNode { + LatLng from; + LatLng to; + VertexNode *next; +}; + +/** @struct VertexGraph + * @brief A data structure to store a graph of vertices + */ +typedef struct { + VertexNode **buckets; + int numBuckets; + int size; + int res; +} VertexGraph; + +void initVertexGraph(VertexGraph *graph, int numBuckets, int res); + +void destroyVertexGraph(VertexGraph *graph); + +VertexNode *addVertexNode(VertexGraph *graph, const LatLng *fromVtx, + const LatLng *toVtx); + +int removeVertexNode(VertexGraph *graph, VertexNode *node); + +VertexNode *findNodeForEdge(const VertexGraph *graph, const LatLng *fromVtx, + const LatLng *toVtx); + +VertexNode *findNodeForVertex(const VertexGraph *graph, const LatLng *fromVtx); + +VertexNode *firstVertexNode(const VertexGraph *graph); + +// Internal functions +uint32_t _hashVertex(const LatLng *vertex, int res, int numBuckets); +void _initVertexNode(VertexNode *node, const LatLng *fromVtx, + const LatLng *toVtx); + +#endif diff --git a/src/h3lib/lib/algos.c b/src/h3lib/lib/algos.c new file mode 100644 index 0000000..79666d8 --- /dev/null +++ b/src/h3lib/lib/algos.c @@ -0,0 +1,1180 @@ +/* + * Copyright 2016-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file algos.c + * @brief Hexagon grid algorithms + */ + +#include "algos.h" + +#include +#include +#include +#include +#include +#include + +#include "alloc.h" +#include "baseCells.h" +#include "bbox.h" +#include "faceijk.h" +#include "h3Assert.h" +#include "h3Index.h" +#include "h3api.h" +#include "latLng.h" +#include "linkedGeo.h" +#include "polygon.h" +#include "vertexGraph.h" + +/* + * Return codes from gridDiskUnsafe and related functions. + */ + +#define MAX_ONE_RING_SIZE 7 +#define POLYGON_TO_CELLS_BUFFER 12 + +/** + * Directions used for traversing a hexagonal ring counterclockwise around + * {1, 0, 0} + * + *
+ *      _
+ *    _/ \\_
+ *   / \\5/ \\
+ *   \\0/ \\4/
+ *   / \\_/ \\
+ *   \\1/ \\3/
+ *     \\2/
+ * 
+ */ +static const Direction DIRECTIONS[6] = {J_AXES_DIGIT, JK_AXES_DIGIT, + K_AXES_DIGIT, IK_AXES_DIGIT, + I_AXES_DIGIT, IJ_AXES_DIGIT}; + +/** + * Direction used for traversing to the next outward hexagonal ring. + */ +static const Direction NEXT_RING_DIRECTION = I_AXES_DIGIT; + +/** + * New digit when traversing along class II grids. + * + * Current digit -> direction -> new digit. + */ +static const Direction NEW_DIGIT_II[7][7] = { + {CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, + IK_AXES_DIGIT, IJ_AXES_DIGIT}, + {K_AXES_DIGIT, I_AXES_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, IK_AXES_DIGIT, + J_AXES_DIGIT, CENTER_DIGIT}, + {J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT, + CENTER_DIGIT, IK_AXES_DIGIT}, + {JK_AXES_DIGIT, IJ_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT, + K_AXES_DIGIT, J_AXES_DIGIT}, + {I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, + JK_AXES_DIGIT, K_AXES_DIGIT}, + {IK_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, JK_AXES_DIGIT, + IJ_AXES_DIGIT, I_AXES_DIGIT}, + {IJ_AXES_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT, J_AXES_DIGIT, K_AXES_DIGIT, + I_AXES_DIGIT, JK_AXES_DIGIT}}; + +/** + * New traversal direction when traversing along class II grids. + * + * Current digit -> direction -> new ap7 move (at coarser level). + */ +static const Direction NEW_ADJUSTMENT_II[7][7] = { + {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, + CENTER_DIGIT, CENTER_DIGIT}, + {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, + IK_AXES_DIGIT, CENTER_DIGIT}, + {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, + CENTER_DIGIT, J_AXES_DIGIT}, + {CENTER_DIGIT, K_AXES_DIGIT, JK_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, + CENTER_DIGIT, CENTER_DIGIT}, + {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, + I_AXES_DIGIT, IJ_AXES_DIGIT}, + {CENTER_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, + IK_AXES_DIGIT, CENTER_DIGIT}, + {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT, + CENTER_DIGIT, IJ_AXES_DIGIT}}; + +/** + * New traversal direction when traversing along class III grids. + * + * Current digit -> direction -> new ap7 move (at coarser level). + */ +static const Direction NEW_DIGIT_III[7][7] = { + {CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, + IK_AXES_DIGIT, IJ_AXES_DIGIT}, + {K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, + IJ_AXES_DIGIT, CENTER_DIGIT}, + {J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, + CENTER_DIGIT, K_AXES_DIGIT}, + {JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, + K_AXES_DIGIT, J_AXES_DIGIT}, + {I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, + J_AXES_DIGIT, JK_AXES_DIGIT}, + {IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, + JK_AXES_DIGIT, I_AXES_DIGIT}, + {IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, + I_AXES_DIGIT, IK_AXES_DIGIT}}; + +/** + * New traversal direction when traversing along class III grids. + * + * Current digit -> direction -> new ap7 move (at coarser level). + */ +static const Direction NEW_ADJUSTMENT_III[7][7] = { + {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, + CENTER_DIGIT, CENTER_DIGIT}, + {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, + K_AXES_DIGIT, CENTER_DIGIT}, + {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, + CENTER_DIGIT, IJ_AXES_DIGIT}, + {CENTER_DIGIT, JK_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, + CENTER_DIGIT, CENTER_DIGIT}, + {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, + IK_AXES_DIGIT, I_AXES_DIGIT}, + {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT, + IK_AXES_DIGIT, CENTER_DIGIT}, + {CENTER_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, + CENTER_DIGIT, IJ_AXES_DIGIT}}; + +/** + * k value which will encompass all cells at resolution 15. + * This is the largest possible k in the H3 grid system. + */ +static const int K_ALL_CELLS_AT_RES_15 = 13780510; + +/** + * Maximum number of cells that result from the gridDisk algorithm with the + * given k. Formula source and proof: https://oeis.org/A003215 + * + * @param k k value, k >= 0. + * @param out size in indexes + */ +H3Error H3_EXPORT(maxGridDiskSize)(int k, int64_t *out) { + if (k < 0) { + return E_DOMAIN; + } + if (k >= K_ALL_CELLS_AT_RES_15) { + // If a k value of this value or above is provided, this function will + // estimate more cells than exist in the H3 grid at the finest + // resolution. This is a problem since the function does signed integer + // arithmetic on `k`, which could overflow. To prevent that, instead + // substitute the maximum number of cells in the grid, as it should not + // be possible for the gridDisk functions to exceed that. Note this is + // not resolution specific. So, when resolution < 15, this function may + // still estimate a size larger than the number of cells in the grid. + return H3_EXPORT(getNumCells)(MAX_H3_RES, out); + } + *out = 3 * (int64_t)k * ((int64_t)k + 1) + 1; + return E_SUCCESS; +} + +/** + * Produce cells within grid distance k of the origin cell. + * + * k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and + * all neighboring cells, and so on. + * + * Output is placed in the provided array in no particular order. Elements of + * the output array may be left zero, as can happen when crossing a pentagon. + * + * @param origin origin cell + * @param k k >= 0 + * @param out zero-filled array which must be of size maxGridDiskSize(k) + */ +H3Error H3_EXPORT(gridDisk)(H3Index origin, int k, H3Index *out) { + return H3_EXPORT(gridDiskDistances)(origin, k, out, NULL); +} + +/** + * Produce cells and their distances from the given origin cell, up to + * distance k. + * + * k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and + * all neighboring cells, and so on. + * + * Output is placed in the provided array in no particular order. Elements of + * the output array may be left zero, as can happen when crossing a pentagon. + * + * @param origin origin cell + * @param k k >= 0 + * @param out zero-filled array which must be of size + * maxGridDiskSize(k) + * @param distances NULL or a zero-filled array which must be of size + * maxGridDiskSize(k) + */ +H3Error H3_EXPORT(gridDiskDistances)(H3Index origin, int k, H3Index *out, + int *distances) { + // Optimistically try the faster gridDiskUnsafe algorithm first + const H3Error failed = + H3_EXPORT(gridDiskDistancesUnsafe)(origin, k, out, distances); + if (failed) { + int64_t maxIdx; + H3Error err = H3_EXPORT(maxGridDiskSize)(k, &maxIdx); + if (err) { + return err; + } + // Fast algo failed, fall back to slower, correct algo + // and also wipe out array because contents untrustworthy + memset(out, 0, maxIdx * sizeof(H3Index)); + + if (distances == NULL) { + distances = H3_MEMORY(calloc)(maxIdx, sizeof(int)); + if (!distances) { + return E_MEMORY_ALLOC; + } + H3Error result = _gridDiskDistancesInternal(origin, k, out, + distances, maxIdx, 0); + H3_MEMORY(free)(distances); + return result; + } else { + memset(distances, 0, maxIdx * sizeof(int)); + return _gridDiskDistancesInternal(origin, k, out, distances, maxIdx, + 0); + } + } else { + return E_SUCCESS; + } +} + +/** + * Internal algorithm for the safe but slow version of gridDiskDistances + * + * Adds the origin cell to the output set (treating it as a hash set) + * and recurses to its neighbors, if needed. + * + * @param origin Origin cell + * @param k Maximum distance to move from the origin + * @param out Array treated as a hash set, elements being either + * H3Index or 0. + * @param distances Scratch area, with elements paralleling the out array. + * Elements indicate ijk distance from the origin cell to + * the output cell + * @param maxIdx Size of out and scratch arrays (must be + * maxGridDiskSize(k)) + * @param curK Current distance from the origin + */ +H3Error _gridDiskDistancesInternal(H3Index origin, int k, H3Index *out, + int *distances, int64_t maxIdx, int curK) { + // Put origin in the output array. out is used as a hash set. + int64_t off = origin % maxIdx; + while (out[off] != 0 && out[off] != origin) { + off = (off + 1) % maxIdx; + } + + // We either got a free slot in the hash set or hit a duplicate + // We might need to process the duplicate anyways because we got + // here on a longer path before. + if (out[off] == origin && distances[off] <= curK) return E_SUCCESS; + + out[off] = origin; + distances[off] = curK; + + // Base case: reached an index k away from the origin. + if (curK >= k) return E_SUCCESS; + + // Recurse to all neighbors in no particular order. + for (int i = 0; i < 6; i++) { + int rotations = 0; + H3Index nextNeighbor; + H3Error neighborResult = h3NeighborRotations(origin, DIRECTIONS[i], + &rotations, &nextNeighbor); + if (neighborResult != E_PENTAGON) { + // E_PENTAGON is an expected case when trying to traverse off of + // pentagons. + if (neighborResult != E_SUCCESS) { + return neighborResult; + } + neighborResult = _gridDiskDistancesInternal( + nextNeighbor, k, out, distances, maxIdx, curK + 1); + if (neighborResult) { + return neighborResult; + } + } + } + return E_SUCCESS; +} + +/** + * Safe but slow version of gridDiskDistances (also called by it when needed). + * + * Adds the origin cell to the output set (treating it as a hash set) + * and recurses to its neighbors, if needed. + * + * @param origin Origin cell + * @param k Maximum distance to move from the origin + * @param out Array treated as a hash set, elements being either + * H3Index or 0. + * @param distances Scratch area, with elements paralleling the out array. + * Elements indicate ijk distance from the origin cell to + * the output cell + */ +H3Error H3_EXPORT(gridDiskDistancesSafe)(H3Index origin, int k, H3Index *out, + int *distances) { + int64_t maxIdx; + H3Error err = H3_EXPORT(maxGridDiskSize)(k, &maxIdx); + if (err) { + return err; + } + return _gridDiskDistancesInternal(origin, k, out, distances, maxIdx, 0); +} + +/** + * Returns the hexagon index neighboring the origin, in the direction dir. + * + * Implementation note: The only reachable case where this returns 0 is if the + * origin is a pentagon and the translation is in the k direction. Thus, + * 0 can only be returned if origin is a pentagon. + * + * @param origin Origin index + * @param dir Direction to move in + * @param rotations Number of ccw rotations to perform to reorient the + * translation vector. Will be modified to the new number of + * rotations to perform (such as when crossing a face edge.) + * @param out H3Index of the specified neighbor if succesful + * @return E_SUCCESS on success + */ +H3Error h3NeighborRotations(H3Index origin, Direction dir, int *rotations, + H3Index *out) { + H3Index current = origin; + + if (dir < CENTER_DIGIT || dir >= INVALID_DIGIT) { + return E_FAILED; + } + // Ensure that rotations is modulo'd by 6 before any possible addition, + // to protect against signed integer overflow. + *rotations = *rotations % 6; + for (int i = 0; i < *rotations; i++) { + dir = _rotate60ccw(dir); + } + + int newRotations = 0; + int oldBaseCell = H3_GET_BASE_CELL(current); + if (NEVER(oldBaseCell < 0) || oldBaseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } + Direction oldLeadingDigit = _h3LeadingNonZeroDigit(current); + + // Adjust the indexing digits and, if needed, the base cell. + int r = H3_GET_RESOLUTION(current) - 1; + while (true) { + if (r == -1) { + H3_SET_BASE_CELL(current, baseCellNeighbors[oldBaseCell][dir]); + newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir]; + + if (H3_GET_BASE_CELL(current) == INVALID_BASE_CELL) { + // Adjust for the deleted k vertex at the base cell level. + // This edge actually borders a different neighbor. + H3_SET_BASE_CELL(current, + baseCellNeighbors[oldBaseCell][IK_AXES_DIGIT]); + newRotations = + baseCellNeighbor60CCWRots[oldBaseCell][IK_AXES_DIGIT]; + + // perform the adjustment for the k-subsequence we're skipping + // over. + current = _h3Rotate60ccw(current); + *rotations = *rotations + 1; + } + + break; + } else { + Direction oldDigit = H3_GET_INDEX_DIGIT(current, r + 1); + Direction nextDir; + if (oldDigit == INVALID_DIGIT) { + // Only possible on invalid input + return E_CELL_INVALID; + } else if (isResolutionClassIII(r + 1)) { + H3_SET_INDEX_DIGIT(current, r + 1, NEW_DIGIT_II[oldDigit][dir]); + nextDir = NEW_ADJUSTMENT_II[oldDigit][dir]; + } else { + H3_SET_INDEX_DIGIT(current, r + 1, + NEW_DIGIT_III[oldDigit][dir]); + nextDir = NEW_ADJUSTMENT_III[oldDigit][dir]; + } + + if (nextDir != CENTER_DIGIT) { + dir = nextDir; + r--; + } else { + // No more adjustment to perform + break; + } + } + } + + int newBaseCell = H3_GET_BASE_CELL(current); + if (_isBaseCellPentagon(newBaseCell)) { + int alreadyAdjustedKSubsequence = 0; + + // force rotation out of missing k-axes sub-sequence + if (_h3LeadingNonZeroDigit(current) == K_AXES_DIGIT) { + if (oldBaseCell != newBaseCell) { + // in this case, we traversed into the deleted + // k subsequence of a pentagon base cell. + // We need to rotate out of that case depending + // on how we got here. + // check for a cw/ccw offset face; default is ccw + + if (ALWAYS(_baseCellIsCwOffset( + newBaseCell, + baseCellData[oldBaseCell].homeFijk.face))) { + current = _h3Rotate60cw(current); + } else { + // See cwOffsetPent in testGridDisk.c for why this is + // unreachable. + current = _h3Rotate60ccw(current); + } + alreadyAdjustedKSubsequence = 1; + } else { + // In this case, we traversed into the deleted + // k subsequence from within the same pentagon + // base cell. + if (oldLeadingDigit == CENTER_DIGIT) { + // Undefined: the k direction is deleted from here + return E_PENTAGON; + } else if (oldLeadingDigit == JK_AXES_DIGIT) { + // Rotate out of the deleted k subsequence + // We also need an additional change to the direction we're + // moving in + current = _h3Rotate60ccw(current); + *rotations = *rotations + 1; + } else if (oldLeadingDigit == IK_AXES_DIGIT) { + // Rotate out of the deleted k subsequence + // We also need an additional change to the direction we're + // moving in + current = _h3Rotate60cw(current); + *rotations = *rotations + 5; + } else { + // TODO: Should never occur, but is reachable by fuzzer + return E_FAILED; + } + } + } + + for (int i = 0; i < newRotations; i++) + current = _h3RotatePent60ccw(current); + + // Account for differing orientation of the base cells (this edge + // might not follow properties of some other edges.) + if (oldBaseCell != newBaseCell) { + if (_isBaseCellPolarPentagon(newBaseCell)) { + // 'polar' base cells behave differently because they have all + // i neighbors. + if (oldBaseCell != 118 && oldBaseCell != 8 && + _h3LeadingNonZeroDigit(current) != JK_AXES_DIGIT) { + *rotations = *rotations + 1; + } + } else if (_h3LeadingNonZeroDigit(current) == IK_AXES_DIGIT && + !alreadyAdjustedKSubsequence) { + // account for distortion introduced to the 5 neighbor by the + // deleted k subsequence. + *rotations = *rotations + 1; + } + } + } else { + for (int i = 0; i < newRotations; i++) + current = _h3Rotate60ccw(current); + } + + *rotations = (*rotations + newRotations) % 6; + *out = current; + + return E_SUCCESS; +} + +/** + * Get the direction from the origin to a given neighbor. This is effectively + * the reverse operation for h3NeighborRotations. Returns INVALID_DIGIT if the + * cells are not neighbors. + * + * TODO: This is currently a brute-force algorithm, but as it's O(6) that's + * probably acceptable. + */ +Direction directionForNeighbor(H3Index origin, H3Index destination) { + bool isPent = H3_EXPORT(isPentagon)(origin); + // Checks each neighbor, in order, to determine which direction the + // destination neighbor is located. Skips CENTER_DIGIT since that + // would be the origin; skips deleted K direction for pentagons. + for (Direction direction = isPent ? J_AXES_DIGIT : K_AXES_DIGIT; + direction < NUM_DIGITS; direction++) { + H3Index neighbor; + int rotations = 0; + H3Error neighborError = + h3NeighborRotations(origin, direction, &rotations, &neighbor); + if (!neighborError && neighbor == destination) { + return direction; + } + } + return INVALID_DIGIT; +} + +/** + * gridDiskUnsafe produces indexes within k distance of the origin index. + * Output behavior is undefined when one of the indexes returned by this + * function is a pentagon or is in the pentagon distortion area. + * + * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and + * all neighboring indexes, and so on. + * + * Output is placed in the provided array in order of increasing distance from + * the origin. + * + * @param origin Origin location. + * @param k k >= 0 + * @param out Array which must be of size maxGridDiskSize(k). + * @return 0 if no pentagon or pentagonal distortion area was encountered. + */ +H3Error H3_EXPORT(gridDiskUnsafe)(H3Index origin, int k, H3Index *out) { + return H3_EXPORT(gridDiskDistancesUnsafe)(origin, k, out, NULL); +} + +/** + * gridDiskDistancesUnsafe produces indexes within k distance of the origin + * index. Output behavior is undefined when one of the indexes returned by this + * function is a pentagon or is in the pentagon distortion area. + * + * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and + * all neighboring indexes, and so on. + * + * Output is placed in the provided array in order of increasing distance from + * the origin. The distances in hexagons is placed in the distances array at + * the same offset. + * + * @param origin Origin location. + * @param k k >= 0 + * @param out Array which must be of size maxGridDiskSize(k). + * @param distances Null or array which must be of size maxGridDiskSize(k). + * @return 0 if no pentagon or pentagonal distortion area was encountered. + */ +H3Error H3_EXPORT(gridDiskDistancesUnsafe)(H3Index origin, int k, H3Index *out, + int *distances) { + // Return codes: + // 1 Pentagon was encountered + // 2 Pentagon distortion (deleted k subsequence) was encountered + // Pentagon being encountered is not itself a problem; really the deleted + // k-subsequence is the problem, but for compatibility reasons we fail on + // the pentagon. + if (k < 0) { + return E_DOMAIN; + } + + // k must be >= 0, so origin is always needed + int idx = 0; + out[idx] = origin; + if (distances) { + distances[idx] = 0; + } + idx++; + + if (H3_EXPORT(isPentagon)(origin)) { + // Pentagon was encountered; bail out as user doesn't want this. + return E_PENTAGON; + } + + // 0 < ring <= k, current ring + int ring = 1; + // 0 <= direction < 6, current side of the ring + int direction = 0; + // 0 <= i < ring, current position on the side of the ring + int i = 0; + // Number of 60 degree ccw rotations to perform on the direction (based on + // which faces have been crossed.) + int rotations = 0; + + while (ring <= k) { + if (direction == 0 && i == 0) { + // Not putting in the output set as it will be done later, at + // the end of this ring. + H3Error neighborResult = h3NeighborRotations( + origin, NEXT_RING_DIRECTION, &rotations, &origin); + if (neighborResult) { + // Should not be possible because `origin` would have to be a + // pentagon + // TODO: Reachable via fuzzer + return neighborResult; + } + + if (H3_EXPORT(isPentagon)(origin)) { + // Pentagon was encountered; bail out as user doesn't want this. + return E_PENTAGON; + } + } + + H3Error neighborResult = h3NeighborRotations( + origin, DIRECTIONS[direction], &rotations, &origin); + if (neighborResult) { + return neighborResult; + } + out[idx] = origin; + if (distances) { + distances[idx] = ring; + } + idx++; + + i++; + // Check if end of this side of the k-ring + if (i == ring) { + i = 0; + direction++; + // Check if end of this ring. + if (direction == 6) { + direction = 0; + ring++; + } + } + + if (H3_EXPORT(isPentagon)(origin)) { + // Pentagon was encountered; bail out as user doesn't want this. + return E_PENTAGON; + } + } + return E_SUCCESS; +} + +/** + * gridDisksUnsafe takes an array of input hex IDs and a max k-ring and returns + * an array of hexagon IDs sorted first by the original hex IDs and then by the + * k-ring (0 to max), with no guaranteed sorting within each k-ring group. + * + * @param h3Set A pointer to an array of H3Indexes + * @param length The total number of H3Indexes in h3Set + * @param k The number of rings to generate + * @param out A pointer to the output memory to dump the new set of H3Indexes to + * The memory block should be equal to maxGridDiskSize(k) * length + * @return 0 if no pentagon is encountered. Cannot trust output otherwise + */ +H3Error H3_EXPORT(gridDisksUnsafe)(H3Index *h3Set, int length, int k, + H3Index *out) { + H3Index *segment; + int64_t segmentSize; + H3Error err = H3_EXPORT(maxGridDiskSize)(k, &segmentSize); + if (err) { + return err; + } + for (int i = 0; i < length; i++) { + // Determine the appropriate segment of the output array to operate on + segment = out + i * segmentSize; + H3Error failed = H3_EXPORT(gridDiskUnsafe)(h3Set[i], k, segment); + if (failed) return failed; + } + return E_SUCCESS; +} + +/** + * Returns the "hollow" ring of hexagons at exactly grid distance k from + * the origin hexagon. In particular, k=0 returns just the origin hexagon. + * + * A nonzero failure code may be returned in some cases, for example, + * if a pentagon is encountered. + * Failure cases may be fixed in future versions. + * + * @param origin Origin location. + * @param k k >= 0 + * @param out Array which must be of size 6 * k (or 1 if k == 0) + * @return 0 if successful; nonzero otherwise. + */ +H3Error H3_EXPORT(gridRingUnsafe)(H3Index origin, int k, H3Index *out) { + // Short-circuit on 'identity' ring + if (k == 0) { + out[0] = origin; + return E_SUCCESS; + } + int idx = 0; + // Number of 60 degree ccw rotations to perform on the direction (based on + // which faces have been crossed.) + int rotations = 0; + // Scratch structure for checking for pentagons + if (H3_EXPORT(isPentagon)(origin)) { + // Pentagon was encountered; bail out as user doesn't want this. + return E_PENTAGON; + } + + for (int ring = 0; ring < k; ring++) { + H3Error neighborResult = h3NeighborRotations( + origin, NEXT_RING_DIRECTION, &rotations, &origin); + if (neighborResult) { + // Should not be possible because `origin` would have to be a + // pentagon + // TODO: Reachable via fuzzer + return neighborResult; + } + + if (H3_EXPORT(isPentagon)(origin)) { + return E_PENTAGON; + } + } + + H3Index lastIndex = origin; + + out[idx] = origin; + idx++; + + for (int direction = 0; direction < 6; direction++) { + for (int pos = 0; pos < k; pos++) { + H3Error neighborResult = h3NeighborRotations( + origin, DIRECTIONS[direction], &rotations, &origin); + if (neighborResult) { + // Should not be possible because `origin` would have to be a + // pentagon + // TODO: Reachable via fuzzer + return neighborResult; + } + + // Skip the very last index, it was already added. We do + // however need to traverse to it because of the pentagonal + // distortion check, below. + if (pos != k - 1 || direction != 5) { + out[idx] = origin; + idx++; + + if (H3_EXPORT(isPentagon)(origin)) { + return E_PENTAGON; + } + } + } + } + + // Check that this matches the expected lastIndex, if it doesn't, + // it indicates pentagonal distortion occurred and we should report + // failure. + if (lastIndex != origin) { + return E_PENTAGON; + } else { + return E_SUCCESS; + } +} + +/** + * maxPolygonToCellsSize returns the number of cells to allocate space for + * when performing a polygonToCells on the given GeoJSON-like data structure. + * + * The size is the maximum of either the number of points in the geoloop or the + * number of cells in the bounding box of the geoloop. + * + * @param geoPolygon A GeoJSON-like data structure indicating the poly to fill + * @param res Hexagon resolution (0-15) + * @param out number of cells to allocate for + * @return 0 (E_SUCCESS) on success. + */ +H3Error H3_EXPORT(maxPolygonToCellsSize)(const GeoPolygon *geoPolygon, int res, + uint32_t flags, int64_t *out) { + if (flags != 0) { + return E_OPTION_INVALID; + } + // Get the bounding box for the GeoJSON-like struct + BBox bbox; + const GeoLoop geoloop = geoPolygon->geoloop; + bboxFromGeoLoop(&geoloop, &bbox); + int64_t numHexagons; + H3Error estimateErr = bboxHexEstimate(&bbox, res, &numHexagons); + if (estimateErr) { + return estimateErr; + } + // This algorithm assumes that the number of vertices is usually less than + // the number of hexagons, but when it's wrong, this will keep it from + // failing + int totalVerts = geoloop.numVerts; + for (int i = 0; i < geoPolygon->numHoles; i++) { + totalVerts += geoPolygon->holes[i].numVerts; + } + if (numHexagons < totalVerts) numHexagons = totalVerts; + // When the polygon is very small, near an icosahedron edge and is an odd + // resolution, the line tracing needs an extra buffer than the estimator + // function provides (but beefing that up to cover causes most situations to + // overallocate memory) + numHexagons += POLYGON_TO_CELLS_BUFFER; + *out = numHexagons; + return E_SUCCESS; +} + +/** + * _getEdgeHexagons takes a given geoloop ring (either the main geoloop or + * one of the holes) and traces it with hexagons and updates the search and + * found memory blocks. This is used for determining the initial hexagon set + * for the polygonToCells algorithm to execute on. + * + * @param geoloop The geoloop (or hole) to be traced + * @param numHexagons The maximum number of hexagons possible for the geoloop + * (also the bounds of the search and found arrays) + * @param res The hexagon resolution (0-15) + * @param numSearchHexes The number of hexagons found so far to be searched + * @param search The block of memory containing the hexagons to search from + * @param found The block of memory containing the hexagons found from the + * search + * + * @return An error code if the hash function cannot insert a found hexagon + * into the found array. + */ +H3Error _getEdgeHexagons(const GeoLoop *geoloop, int64_t numHexagons, int res, + int64_t *numSearchHexes, H3Index *search, + H3Index *found) { + for (int i = 0; i < geoloop->numVerts; i++) { + LatLng origin = geoloop->verts[i]; + LatLng destination = i == geoloop->numVerts - 1 ? geoloop->verts[0] + : geoloop->verts[i + 1]; + int64_t numHexesEstimate; + H3Error estimateErr = + lineHexEstimate(&origin, &destination, res, &numHexesEstimate); + if (estimateErr) { + return estimateErr; + } + for (int64_t j = 0; j < numHexesEstimate; j++) { + LatLng interpolate; + interpolate.lat = + (origin.lat * (numHexesEstimate - j) / numHexesEstimate) + + (destination.lat * j / numHexesEstimate); + interpolate.lng = + (origin.lng * (numHexesEstimate - j) / numHexesEstimate) + + (destination.lng * j / numHexesEstimate); + H3Index pointHex; + H3Error e = H3_EXPORT(latLngToCell)(&interpolate, res, &pointHex); + if (e) { + return e; + } + // A simple hash to store the hexagon, or move to another place if + // needed + int64_t loc = (int64_t)(pointHex % numHexagons); + int64_t loopCount = 0; + while (found[loc] != 0) { + // If this conditional is reached, the `found` memory block is + // too small for the given polygon. This should not happen. + // TODO: Reachable via fuzzer + if (loopCount > numHexagons) return E_FAILED; + if (found[loc] == pointHex) + break; // At least two points of the geoloop index to the + // same cell + loc = (loc + 1) % numHexagons; + loopCount++; + } + if (found[loc] == pointHex) + continue; // Skip this hex, already exists in the found hash + // Otherwise, set it in the found hash for now + found[loc] = pointHex; + + search[*numSearchHexes] = pointHex; + (*numSearchHexes)++; + } + } + return E_SUCCESS; +} + +/** + * polygonToCells takes a given GeoJSON-like data structure and preallocated, + * zeroed memory, and fills it with the hexagons that are contained by + * the GeoJSON-like data structure. + * + * This implementation traces the GeoJSON geoloop(s) in cartesian space with + * hexagons, tests them and their neighbors to be contained by the geoloop(s), + * and then any newly found hexagons are used to test again until no new + * hexagons are found. + * + * @param geoPolygon The geoloop and holes defining the relevant area + * @param res The Hexagon resolution (0-15) + * @param out The slab of zeroed memory to write to. Assumed to be big enough. + */ +H3Error H3_EXPORT(polygonToCells)(const GeoPolygon *geoPolygon, int res, + uint32_t flags, H3Index *out) { + if (flags != 0) { + return E_OPTION_INVALID; + } + // One of the goals of the polygonToCells algorithm is that two adjacent + // polygons with zero overlap have zero overlapping hexagons. That the + // hexagons are uniquely assigned. There are a few approaches to take here, + // such as deciding based on which polygon has the greatest overlapping area + // of the hexagon, or the most number of contained points on the hexagon + // (using the center point as a tiebreaker). + // + // But if the polygons are convex, both of these more complex algorithms can + // be reduced down to checking whether or not the center of the hexagon is + // contained in the polygon, and so this is the approach that this + // polygonToCells algorithm will follow, as it's simpler, faster, and the + // error for concave polygons is still minimal (only affecting concave + // shapes on the order of magnitude of the hexagon size or smaller, not + // impacting larger concave shapes) + // + // This first part is identical to the maxPolygonToCellsSize above. + + // Get the bounding boxes for the polygon and any holes + BBox *bboxes = H3_MEMORY(malloc)((geoPolygon->numHoles + 1) * sizeof(BBox)); + if (!bboxes) { + return E_MEMORY_ALLOC; + } + bboxesFromGeoPolygon(geoPolygon, bboxes); + + // Get the estimated number of hexagons and allocate some temporary memory + // for the hexagons + int64_t numHexagons; + H3Error numHexagonsError = + H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &numHexagons); + if (numHexagonsError) { + H3_MEMORY(free)(bboxes); + return numHexagonsError; + } + H3Index *search = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index)); + if (!search) { + H3_MEMORY(free)(bboxes); + return E_MEMORY_ALLOC; + } + H3Index *found = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index)); + if (!found) { + H3_MEMORY(free)(bboxes); + H3_MEMORY(free)(search); + return E_MEMORY_ALLOC; + } + + // Some metadata for tracking the state of the search and found memory + // blocks + int64_t numSearchHexes = 0; + int64_t numFoundHexes = 0; + + // 1. Trace the hexagons along the polygon defining the outer geoloop and + // add them to the search hash. The hexagon containing the geoloop point + // may or may not be contained by the geoloop (as the hexagon's center + // point may be outside of the boundary.) + const GeoLoop geoloop = geoPolygon->geoloop; + H3Error edgeHexError = _getEdgeHexagons(&geoloop, numHexagons, res, + &numSearchHexes, search, found); + // If this branch is reached, we have exceeded the maximum number of + // hexagons possible and need to clean up the allocated memory. + // TODO: Reachable via fuzzer + if (edgeHexError) { + H3_MEMORY(free)(search); + H3_MEMORY(free)(found); + H3_MEMORY(free)(bboxes); + return edgeHexError; + } + + // 2. Iterate over all holes, trace the polygons defining the holes with + // hexagons and add to only the search hash. We're going to temporarily use + // the `found` hash to use for dedupe purposes and then re-zero it once + // we're done here, otherwise we'd have to scan the whole set on each insert + // to make sure there's no duplicates, which is very inefficient. + for (int i = 0; i < geoPolygon->numHoles; i++) { + GeoLoop *hole = &(geoPolygon->holes[i]); + edgeHexError = _getEdgeHexagons(hole, numHexagons, res, &numSearchHexes, + search, found); + // If this branch is reached, we have exceeded the maximum number of + // hexagons possible and need to clean up the allocated memory. + // TODO: Reachable via fuzzer + if (edgeHexError) { + H3_MEMORY(free)(search); + H3_MEMORY(free)(found); + H3_MEMORY(free)(bboxes); + return edgeHexError; + } + } + + // 3. Re-zero the found hash so it can be used in the main loop below + for (int64_t i = 0; i < numHexagons; i++) found[i] = H3_NULL; + + // 4. Begin main loop. While the search hash is not empty do the following + while (numSearchHexes > 0) { + // Iterate through all hexagons in the current search hash, then loop + // through all neighbors and test Point-in-Poly, if point-in-poly + // succeeds, add to out and found hashes if not already there. + int64_t currentSearchNum = 0; + int64_t i = 0; + while (currentSearchNum < numSearchHexes) { + H3Index ring[MAX_ONE_RING_SIZE] = {0}; + H3Index searchHex = search[i]; + H3_EXPORT(gridDisk)(searchHex, 1, ring); + for (int j = 0; j < MAX_ONE_RING_SIZE; j++) { + if (ring[j] == H3_NULL) { + continue; // Skip if this was a pentagon and only had 5 + // neighbors + } + + H3Index hex = ring[j]; + + // A simple hash to store the hexagon, or move to another place + // if needed. This MUST be done before the point-in-poly check + // since that's far more expensive + int64_t loc = (int64_t)(hex % numHexagons); + int64_t loopCount = 0; + while (out[loc] != 0) { + // If this branch is reached, we have exceeded the maximum + // number of hexagons possible and need to clean up the + // allocated memory. + // TODO: Reachable via fuzzer + if (loopCount > numHexagons) { + H3_MEMORY(free)(search); + H3_MEMORY(free)(found); + H3_MEMORY(free)(bboxes); + return E_FAILED; + } + if (out[loc] == hex) break; // Skip duplicates found + loc = (loc + 1) % numHexagons; + loopCount++; + } + if (out[loc] == hex) { + continue; // Skip this hex, already exists in the out hash + } + + // Check if the hexagon is in the polygon or not + LatLng hexCenter; + H3_EXPORT(cellToLatLng)(hex, &hexCenter); + + // If not, skip + if (!pointInsidePolygon(geoPolygon, bboxes, &hexCenter)) { + continue; + } + + // Otherwise set it in the output array + out[loc] = hex; + + // Set the hexagon in the found hash + found[numFoundHexes] = hex; + numFoundHexes++; + } + currentSearchNum++; + i++; + } + + // Swap the search and found pointers, copy the found hex count to the + // search hex count, and zero everything related to the found memory. + H3Index *temp = search; + search = found; + found = temp; + for (int64_t j = 0; j < numSearchHexes; j++) found[j] = 0; + numSearchHexes = numFoundHexes; + numFoundHexes = 0; + // Repeat until no new hexagons are found + } + // The out memory structure should be complete, end it here + H3_MEMORY(free)(bboxes); + H3_MEMORY(free)(search); + H3_MEMORY(free)(found); + return E_SUCCESS; +} + +/** + * Internal: Create a vertex graph from a set of hexagons. It is the + * responsibility of the caller to call destroyVertexGraph on the populated + * graph, otherwise the memory in the graph nodes will not be freed. + * @private + * @param h3Set Set of hexagons + * @param numHexes Number of hexagons in the set + * @param graph Output graph + */ +H3Error h3SetToVertexGraph(const H3Index *h3Set, const int numHexes, + VertexGraph *graph) { + CellBoundary vertices; + LatLng *fromVtx; + LatLng *toVtx; + VertexNode *edge; + if (numHexes < 1) { + // We still need to init the graph, or calls to destroyVertexGraph will + // fail + initVertexGraph(graph, 0, 0); + return E_SUCCESS; + } + int res = H3_GET_RESOLUTION(h3Set[0]); + const int minBuckets = 6; + // TODO: Better way to calculate/guess? + int numBuckets = numHexes > minBuckets ? numHexes : minBuckets; + initVertexGraph(graph, numBuckets, res); + // Iterate through every hexagon + for (int i = 0; i < numHexes; i++) { + H3Error boundaryErr = H3_EXPORT(cellToBoundary)(h3Set[i], &vertices); + if (boundaryErr) { + // Destroy vertex graph as caller will not know to do so. + destroyVertexGraph(graph); + return boundaryErr; + } + // iterate through every edge + for (int j = 0; j < vertices.numVerts; j++) { + fromVtx = &vertices.verts[j]; + toVtx = &vertices.verts[(j + 1) % vertices.numVerts]; + // If we've seen this edge already, it will be reversed + edge = findNodeForEdge(graph, toVtx, fromVtx); + if (edge != NULL) { + // If we've seen it, drop it. No edge is shared by more than 2 + // hexagons, so we'll never see it again. + removeVertexNode(graph, edge); + } else { + // Add a new node for this edge + addVertexNode(graph, fromVtx, toVtx); + } + } + } + return E_SUCCESS; +} + +/** + * Internal: Create a LinkedGeoPolygon from a vertex graph. It is the + * responsibility of the caller to call destroyLinkedMultiPolygon on the + * populated linked geo structure, or the memory for that structure will not be + * freed. + * @private + * @param graph Input graph + * @param out Output polygon + */ +void _vertexGraphToLinkedGeo(VertexGraph *graph, LinkedGeoPolygon *out) { + *out = (LinkedGeoPolygon){0}; + LinkedGeoLoop *loop; + VertexNode *edge; + LatLng nextVtx; + // Find the next unused entry point + while ((edge = firstVertexNode(graph)) != NULL) { + loop = addNewLinkedLoop(out); + // Walk the graph to get the outline + do { + addLinkedCoord(loop, &edge->from); + nextVtx = edge->to; + // Remove frees the node, so we can't use edge after this + removeVertexNode(graph, edge); + edge = findNodeForVertex(graph, &nextVtx); + } while (edge); + } +} + +/** + * Create a LinkedGeoPolygon describing the outline(s) of a set of hexagons. + * Polygon outlines will follow GeoJSON MultiPolygon order: Each polygon will + * have one outer loop, which is first in the list, followed by any holes. + * + * It is the responsibility of the caller to call destroyLinkedMultiPolygon on + * the populated linked geo structure, or the memory for that structure will not + * be freed. + * + * It is expected that all hexagons in the set have the same resolution and + * that the set contains no duplicates. Behavior is undefined if duplicates + * or multiple resolutions are present, and the algorithm may produce + * unexpected or invalid output. + * + * @param h3Set Set of hexagons + * @param numHexes Number of hexagons in set + * @param out Output polygon + */ +H3Error H3_EXPORT(cellsToLinkedMultiPolygon)(const H3Index *h3Set, + const int numHexes, + LinkedGeoPolygon *out) { + VertexGraph graph; + H3Error err = h3SetToVertexGraph(h3Set, numHexes, &graph); + if (err) { + return err; + } + _vertexGraphToLinkedGeo(&graph, out); + destroyVertexGraph(&graph); + H3Error normalizeResult = normalizeMultiPolygon(out); + if (normalizeResult) { + H3_EXPORT(destroyLinkedMultiPolygon)(out); + } + return normalizeResult; +} diff --git a/src/h3lib/lib/baseCells.c b/src/h3lib/lib/baseCells.c new file mode 100644 index 0000000..57e6438 --- /dev/null +++ b/src/h3lib/lib/baseCells.c @@ -0,0 +1,939 @@ +/* + * Copyright 2016-2020 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file baseCells.c + * @brief Base cell related lookup tables and access functions. + */ + +#include "baseCells.h" + +#include "h3Index.h" + +/** @struct BaseCellRotation + * @brief base cell at a given ijk and required rotations into its system + */ +typedef struct { + int baseCell; ///< base cell number + int ccwRot60; ///< number of ccw 60 degree rotations relative to current + /// face +} BaseCellRotation; + +/** @brief Neighboring base cell ID in each IJK direction. + * + * For each base cell, for each direction, the neighboring base + * cell ID is given. 127 indicates there is no neighbor in that direction. + */ +const int baseCellNeighbors[NUM_BASE_CELLS][7] = { + {0, 1, 5, 2, 4, 3, 8}, // base cell 0 + {1, 7, 6, 9, 0, 3, 2}, // base cell 1 + {2, 6, 10, 11, 0, 1, 5}, // base cell 2 + {3, 13, 1, 7, 4, 12, 0}, // base cell 3 + {4, INVALID_BASE_CELL, 15, 8, 3, 0, 12}, // base cell 4 (pentagon) + {5, 2, 18, 10, 8, 0, 16}, // base cell 5 + {6, 14, 11, 17, 1, 9, 2}, // base cell 6 + {7, 21, 9, 19, 3, 13, 1}, // base cell 7 + {8, 5, 22, 16, 4, 0, 15}, // base cell 8 + {9, 19, 14, 20, 1, 7, 6}, // base cell 9 + {10, 11, 24, 23, 5, 2, 18}, // base cell 10 + {11, 17, 23, 25, 2, 6, 10}, // base cell 11 + {12, 28, 13, 26, 4, 15, 3}, // base cell 12 + {13, 26, 21, 29, 3, 12, 7}, // base cell 13 + {14, INVALID_BASE_CELL, 17, 27, 9, 20, 6}, // base cell 14 (pentagon) + {15, 22, 28, 31, 4, 8, 12}, // base cell 15 + {16, 18, 33, 30, 8, 5, 22}, // base cell 16 + {17, 11, 14, 6, 35, 25, 27}, // base cell 17 + {18, 24, 30, 32, 5, 10, 16}, // base cell 18 + {19, 34, 20, 36, 7, 21, 9}, // base cell 19 + {20, 14, 19, 9, 40, 27, 36}, // base cell 20 + {21, 38, 19, 34, 13, 29, 7}, // base cell 21 + {22, 16, 41, 33, 15, 8, 31}, // base cell 22 + {23, 24, 11, 10, 39, 37, 25}, // base cell 23 + {24, INVALID_BASE_CELL, 32, 37, 10, 23, 18}, // base cell 24 (pentagon) + {25, 23, 17, 11, 45, 39, 35}, // base cell 25 + {26, 42, 29, 43, 12, 28, 13}, // base cell 26 + {27, 40, 35, 46, 14, 20, 17}, // base cell 27 + {28, 31, 42, 44, 12, 15, 26}, // base cell 28 + {29, 43, 38, 47, 13, 26, 21}, // base cell 29 + {30, 32, 48, 50, 16, 18, 33}, // base cell 30 + {31, 41, 44, 53, 15, 22, 28}, // base cell 31 + {32, 30, 24, 18, 52, 50, 37}, // base cell 32 + {33, 30, 49, 48, 22, 16, 41}, // base cell 33 + {34, 19, 38, 21, 54, 36, 51}, // base cell 34 + {35, 46, 45, 56, 17, 27, 25}, // base cell 35 + {36, 20, 34, 19, 55, 40, 54}, // base cell 36 + {37, 39, 52, 57, 24, 23, 32}, // base cell 37 + {38, INVALID_BASE_CELL, 34, 51, 29, 47, 21}, // base cell 38 (pentagon) + {39, 37, 25, 23, 59, 57, 45}, // base cell 39 + {40, 27, 36, 20, 60, 46, 55}, // base cell 40 + {41, 49, 53, 61, 22, 33, 31}, // base cell 41 + {42, 58, 43, 62, 28, 44, 26}, // base cell 42 + {43, 62, 47, 64, 26, 42, 29}, // base cell 43 + {44, 53, 58, 65, 28, 31, 42}, // base cell 44 + {45, 39, 35, 25, 63, 59, 56}, // base cell 45 + {46, 60, 56, 68, 27, 40, 35}, // base cell 46 + {47, 38, 43, 29, 69, 51, 64}, // base cell 47 + {48, 49, 30, 33, 67, 66, 50}, // base cell 48 + {49, INVALID_BASE_CELL, 61, 66, 33, 48, 41}, // base cell 49 (pentagon) + {50, 48, 32, 30, 70, 67, 52}, // base cell 50 + {51, 69, 54, 71, 38, 47, 34}, // base cell 51 + {52, 57, 70, 74, 32, 37, 50}, // base cell 52 + {53, 61, 65, 75, 31, 41, 44}, // base cell 53 + {54, 71, 55, 73, 34, 51, 36}, // base cell 54 + {55, 40, 54, 36, 72, 60, 73}, // base cell 55 + {56, 68, 63, 77, 35, 46, 45}, // base cell 56 + {57, 59, 74, 78, 37, 39, 52}, // base cell 57 + {58, INVALID_BASE_CELL, 62, 76, 44, 65, 42}, // base cell 58 (pentagon) + {59, 63, 78, 79, 39, 45, 57}, // base cell 59 + {60, 72, 68, 80, 40, 55, 46}, // base cell 60 + {61, 53, 49, 41, 81, 75, 66}, // base cell 61 + {62, 43, 58, 42, 82, 64, 76}, // base cell 62 + {63, INVALID_BASE_CELL, 56, 45, 79, 59, 77}, // base cell 63 (pentagon) + {64, 47, 62, 43, 84, 69, 82}, // base cell 64 + {65, 58, 53, 44, 86, 76, 75}, // base cell 65 + {66, 67, 81, 85, 49, 48, 61}, // base cell 66 + {67, 66, 50, 48, 87, 85, 70}, // base cell 67 + {68, 56, 60, 46, 90, 77, 80}, // base cell 68 + {69, 51, 64, 47, 89, 71, 84}, // base cell 69 + {70, 67, 52, 50, 83, 87, 74}, // base cell 70 + {71, 89, 73, 91, 51, 69, 54}, // base cell 71 + {72, INVALID_BASE_CELL, 73, 55, 80, 60, 88}, // base cell 72 (pentagon) + {73, 91, 72, 88, 54, 71, 55}, // base cell 73 + {74, 78, 83, 92, 52, 57, 70}, // base cell 74 + {75, 65, 61, 53, 94, 86, 81}, // base cell 75 + {76, 86, 82, 96, 58, 65, 62}, // base cell 76 + {77, 63, 68, 56, 93, 79, 90}, // base cell 77 + {78, 74, 59, 57, 95, 92, 79}, // base cell 78 + {79, 78, 63, 59, 93, 95, 77}, // base cell 79 + {80, 68, 72, 60, 99, 90, 88}, // base cell 80 + {81, 85, 94, 101, 61, 66, 75}, // base cell 81 + {82, 96, 84, 98, 62, 76, 64}, // base cell 82 + {83, INVALID_BASE_CELL, 74, 70, 100, 87, 92}, // base cell 83 (pentagon) + {84, 69, 82, 64, 97, 89, 98}, // base cell 84 + {85, 87, 101, 102, 66, 67, 81}, // base cell 85 + {86, 76, 75, 65, 104, 96, 94}, // base cell 86 + {87, 83, 102, 100, 67, 70, 85}, // base cell 87 + {88, 72, 91, 73, 99, 80, 105}, // base cell 88 + {89, 97, 91, 103, 69, 84, 71}, // base cell 89 + {90, 77, 80, 68, 106, 93, 99}, // base cell 90 + {91, 73, 89, 71, 105, 88, 103}, // base cell 91 + {92, 83, 78, 74, 108, 100, 95}, // base cell 92 + {93, 79, 90, 77, 109, 95, 106}, // base cell 93 + {94, 86, 81, 75, 107, 104, 101}, // base cell 94 + {95, 92, 79, 78, 109, 108, 93}, // base cell 95 + {96, 104, 98, 110, 76, 86, 82}, // base cell 96 + {97, INVALID_BASE_CELL, 98, 84, 103, 89, 111}, // base cell 97 (pentagon) + {98, 110, 97, 111, 82, 96, 84}, // base cell 98 + {99, 80, 105, 88, 106, 90, 113}, // base cell 99 + {100, 102, 83, 87, 108, 114, 92}, // base cell 100 + {101, 102, 107, 112, 81, 85, 94}, // base cell 101 + {102, 101, 87, 85, 114, 112, 100}, // base cell 102 + {103, 91, 97, 89, 116, 105, 111}, // base cell 103 + {104, 107, 110, 115, 86, 94, 96}, // base cell 104 + {105, 88, 103, 91, 113, 99, 116}, // base cell 105 + {106, 93, 99, 90, 117, 109, 113}, // base cell 106 + {107, INVALID_BASE_CELL, 101, 94, 115, 104, + 112}, // base cell 107 (pentagon) + {108, 100, 95, 92, 118, 114, 109}, // base cell 108 + {109, 108, 93, 95, 117, 118, 106}, // base cell 109 + {110, 98, 104, 96, 119, 111, 115}, // base cell 110 + {111, 97, 110, 98, 116, 103, 119}, // base cell 111 + {112, 107, 102, 101, 120, 115, 114}, // base cell 112 + {113, 99, 116, 105, 117, 106, 121}, // base cell 113 + {114, 112, 100, 102, 118, 120, 108}, // base cell 114 + {115, 110, 107, 104, 120, 119, 112}, // base cell 115 + {116, 103, 119, 111, 113, 105, 121}, // base cell 116 + {117, INVALID_BASE_CELL, 109, 118, 113, 121, + 106}, // base cell 117 (pentagon) + {118, 120, 108, 114, 117, 121, 109}, // base cell 118 + {119, 111, 115, 110, 121, 116, 120}, // base cell 119 + {120, 115, 114, 112, 121, 119, 118}, // base cell 120 + {121, 116, 120, 119, 117, 113, 118}, // base cell 121 +}; + +/** @brief Neighboring base cell rotations in each IJK direction. + * + * For each base cell, for each direction, the number of 60 degree + * CCW rotations to the coordinate system of the neighbor is given. + * -1 indicates there is no neighbor in that direction. + */ +const int baseCellNeighbor60CCWRots[NUM_BASE_CELLS][7] = { + {0, 5, 0, 0, 1, 5, 1}, // base cell 0 + {0, 0, 1, 0, 1, 0, 1}, // base cell 1 + {0, 0, 0, 0, 0, 5, 0}, // base cell 2 + {0, 5, 0, 0, 2, 5, 1}, // base cell 3 + {0, -1, 1, 0, 3, 4, 2}, // base cell 4 (pentagon) + {0, 0, 1, 0, 1, 0, 1}, // base cell 5 + {0, 0, 0, 3, 5, 5, 0}, // base cell 6 + {0, 0, 0, 0, 0, 5, 0}, // base cell 7 + {0, 5, 0, 0, 0, 5, 1}, // base cell 8 + {0, 0, 1, 3, 0, 0, 1}, // base cell 9 + {0, 0, 1, 3, 0, 0, 1}, // base cell 10 + {0, 3, 3, 3, 0, 0, 0}, // base cell 11 + {0, 5, 0, 0, 3, 5, 1}, // base cell 12 + {0, 0, 1, 0, 1, 0, 1}, // base cell 13 + {0, -1, 3, 0, 5, 2, 0}, // base cell 14 (pentagon) + {0, 5, 0, 0, 4, 5, 1}, // base cell 15 + {0, 0, 0, 0, 0, 5, 0}, // base cell 16 + {0, 3, 3, 3, 3, 0, 3}, // base cell 17 + {0, 0, 0, 3, 5, 5, 0}, // base cell 18 + {0, 3, 3, 3, 0, 0, 0}, // base cell 19 + {0, 3, 3, 3, 0, 3, 0}, // base cell 20 + {0, 0, 0, 3, 5, 5, 0}, // base cell 21 + {0, 0, 1, 0, 1, 0, 1}, // base cell 22 + {0, 3, 3, 3, 0, 3, 0}, // base cell 23 + {0, -1, 3, 0, 5, 2, 0}, // base cell 24 (pentagon) + {0, 0, 0, 3, 0, 0, 3}, // base cell 25 + {0, 0, 0, 0, 0, 5, 0}, // base cell 26 + {0, 3, 0, 0, 0, 3, 3}, // base cell 27 + {0, 0, 1, 0, 1, 0, 1}, // base cell 28 + {0, 0, 1, 3, 0, 0, 1}, // base cell 29 + {0, 3, 3, 3, 0, 0, 0}, // base cell 30 + {0, 0, 0, 0, 0, 5, 0}, // base cell 31 + {0, 3, 3, 3, 3, 0, 3}, // base cell 32 + {0, 0, 1, 3, 0, 0, 1}, // base cell 33 + {0, 3, 3, 3, 3, 0, 3}, // base cell 34 + {0, 0, 3, 0, 3, 0, 3}, // base cell 35 + {0, 0, 0, 3, 0, 0, 3}, // base cell 36 + {0, 3, 0, 0, 0, 3, 3}, // base cell 37 + {0, -1, 3, 0, 5, 2, 0}, // base cell 38 (pentagon) + {0, 3, 0, 0, 3, 3, 0}, // base cell 39 + {0, 3, 0, 0, 3, 3, 0}, // base cell 40 + {0, 0, 0, 3, 5, 5, 0}, // base cell 41 + {0, 0, 0, 3, 5, 5, 0}, // base cell 42 + {0, 3, 3, 3, 0, 0, 0}, // base cell 43 + {0, 0, 1, 3, 0, 0, 1}, // base cell 44 + {0, 0, 3, 0, 0, 3, 3}, // base cell 45 + {0, 0, 0, 3, 0, 3, 0}, // base cell 46 + {0, 3, 3, 3, 0, 3, 0}, // base cell 47 + {0, 3, 3, 3, 0, 3, 0}, // base cell 48 + {0, -1, 3, 0, 5, 2, 0}, // base cell 49 (pentagon) + {0, 0, 0, 3, 0, 0, 3}, // base cell 50 + {0, 3, 0, 0, 0, 3, 3}, // base cell 51 + {0, 0, 3, 0, 3, 0, 3}, // base cell 52 + {0, 3, 3, 3, 0, 0, 0}, // base cell 53 + {0, 0, 3, 0, 3, 0, 3}, // base cell 54 + {0, 0, 3, 0, 0, 3, 3}, // base cell 55 + {0, 3, 3, 3, 0, 0, 3}, // base cell 56 + {0, 0, 0, 3, 0, 3, 0}, // base cell 57 + {0, -1, 3, 0, 5, 2, 0}, // base cell 58 (pentagon) + {0, 3, 3, 3, 3, 3, 0}, // base cell 59 + {0, 3, 3, 3, 3, 3, 0}, // base cell 60 + {0, 3, 3, 3, 3, 0, 3}, // base cell 61 + {0, 3, 3, 3, 3, 0, 3}, // base cell 62 + {0, -1, 3, 0, 5, 2, 0}, // base cell 63 (pentagon) + {0, 0, 0, 3, 0, 0, 3}, // base cell 64 + {0, 3, 3, 3, 0, 3, 0}, // base cell 65 + {0, 3, 0, 0, 0, 3, 3}, // base cell 66 + {0, 3, 0, 0, 3, 3, 0}, // base cell 67 + {0, 3, 3, 3, 0, 0, 0}, // base cell 68 + {0, 3, 0, 0, 3, 3, 0}, // base cell 69 + {0, 0, 3, 0, 0, 3, 3}, // base cell 70 + {0, 0, 0, 3, 0, 3, 0}, // base cell 71 + {0, -1, 3, 0, 5, 2, 0}, // base cell 72 (pentagon) + {0, 3, 3, 3, 0, 0, 3}, // base cell 73 + {0, 3, 3, 3, 0, 0, 3}, // base cell 74 + {0, 0, 0, 3, 0, 0, 3}, // base cell 75 + {0, 3, 0, 0, 0, 3, 3}, // base cell 76 + {0, 0, 0, 3, 0, 5, 0}, // base cell 77 + {0, 3, 3, 3, 0, 0, 0}, // base cell 78 + {0, 0, 1, 3, 1, 0, 1}, // base cell 79 + {0, 0, 1, 3, 1, 0, 1}, // base cell 80 + {0, 0, 3, 0, 3, 0, 3}, // base cell 81 + {0, 0, 3, 0, 3, 0, 3}, // base cell 82 + {0, -1, 3, 0, 5, 2, 0}, // base cell 83 (pentagon) + {0, 0, 3, 0, 0, 3, 3}, // base cell 84 + {0, 0, 0, 3, 0, 3, 0}, // base cell 85 + {0, 3, 0, 0, 3, 3, 0}, // base cell 86 + {0, 3, 3, 3, 3, 3, 0}, // base cell 87 + {0, 0, 0, 3, 0, 5, 0}, // base cell 88 + {0, 3, 3, 3, 3, 3, 0}, // base cell 89 + {0, 0, 0, 0, 0, 0, 1}, // base cell 90 + {0, 3, 3, 3, 0, 0, 0}, // base cell 91 + {0, 0, 0, 3, 0, 5, 0}, // base cell 92 + {0, 5, 0, 0, 5, 5, 0}, // base cell 93 + {0, 0, 3, 0, 0, 3, 3}, // base cell 94 + {0, 0, 0, 0, 0, 0, 1}, // base cell 95 + {0, 0, 0, 3, 0, 3, 0}, // base cell 96 + {0, -1, 3, 0, 5, 2, 0}, // base cell 97 (pentagon) + {0, 3, 3, 3, 0, 0, 3}, // base cell 98 + {0, 5, 0, 0, 5, 5, 0}, // base cell 99 + {0, 0, 1, 3, 1, 0, 1}, // base cell 100 + {0, 3, 3, 3, 0, 0, 3}, // base cell 101 + {0, 3, 3, 3, 0, 0, 0}, // base cell 102 + {0, 0, 1, 3, 1, 0, 1}, // base cell 103 + {0, 3, 3, 3, 3, 3, 0}, // base cell 104 + {0, 0, 0, 0, 0, 0, 1}, // base cell 105 + {0, 0, 1, 0, 3, 5, 1}, // base cell 106 + {0, -1, 3, 0, 5, 2, 0}, // base cell 107 (pentagon) + {0, 5, 0, 0, 5, 5, 0}, // base cell 108 + {0, 0, 1, 0, 4, 5, 1}, // base cell 109 + {0, 3, 3, 3, 0, 0, 0}, // base cell 110 + {0, 0, 0, 3, 0, 5, 0}, // base cell 111 + {0, 0, 0, 3, 0, 5, 0}, // base cell 112 + {0, 0, 1, 0, 2, 5, 1}, // base cell 113 + {0, 0, 0, 0, 0, 0, 1}, // base cell 114 + {0, 0, 1, 3, 1, 0, 1}, // base cell 115 + {0, 5, 0, 0, 5, 5, 0}, // base cell 116 + {0, -1, 1, 0, 3, 4, 2}, // base cell 117 (pentagon) + {0, 0, 1, 0, 0, 5, 1}, // base cell 118 + {0, 0, 0, 0, 0, 0, 1}, // base cell 119 + {0, 5, 0, 0, 5, 5, 0}, // base cell 120 + {0, 0, 1, 0, 1, 5, 1}, // base cell 121 +}; + +/** @brief Resolution 0 base cell lookup table for each face. + * + * Given the face number and a resolution 0 ijk+ coordinate in that face's + * face-centered ijk coordinate system, gives the base cell located at that + * coordinate and the number of 60 ccw rotations to rotate into that base + * cell's orientation. + * + * Valid lookup coordinates are from (0, 0, 0) to (2, 2, 2). + * + * This table can be accessed using the functions `_faceIjkToBaseCell` and + * `_faceIjkToBaseCellCCWrot60` + */ +static const BaseCellRotation faceIjkBaseCells[NUM_ICOSA_FACES][3][3][3] = { + {// face 0 + { + // i 0 + {{16, 0}, {18, 0}, {24, 0}}, // j 0 + {{33, 0}, {30, 0}, {32, 3}}, // j 1 + {{49, 1}, {48, 3}, {50, 3}} // j 2 + }, + { + // i 1 + {{8, 0}, {5, 5}, {10, 5}}, // j 0 + {{22, 0}, {16, 0}, {18, 0}}, // j 1 + {{41, 1}, {33, 0}, {30, 0}} // j 2 + }, + { + // i 2 + {{4, 0}, {0, 5}, {2, 5}}, // j 0 + {{15, 1}, {8, 0}, {5, 5}}, // j 1 + {{31, 1}, {22, 0}, {16, 0}} // j 2 + }}, + {// face 1 + { + // i 0 + {{2, 0}, {6, 0}, {14, 0}}, // j 0 + {{10, 0}, {11, 0}, {17, 3}}, // j 1 + {{24, 1}, {23, 3}, {25, 3}} // j 2 + }, + { + // i 1 + {{0, 0}, {1, 5}, {9, 5}}, // j 0 + {{5, 0}, {2, 0}, {6, 0}}, // j 1 + {{18, 1}, {10, 0}, {11, 0}} // j 2 + }, + { + // i 2 + {{4, 1}, {3, 5}, {7, 5}}, // j 0 + {{8, 1}, {0, 0}, {1, 5}}, // j 1 + {{16, 1}, {5, 0}, {2, 0}} // j 2 + }}, + {// face 2 + { + // i 0 + {{7, 0}, {21, 0}, {38, 0}}, // j 0 + {{9, 0}, {19, 0}, {34, 3}}, // j 1 + {{14, 1}, {20, 3}, {36, 3}} // j 2 + }, + { + // i 1 + {{3, 0}, {13, 5}, {29, 5}}, // j 0 + {{1, 0}, {7, 0}, {21, 0}}, // j 1 + {{6, 1}, {9, 0}, {19, 0}} // j 2 + }, + { + // i 2 + {{4, 2}, {12, 5}, {26, 5}}, // j 0 + {{0, 1}, {3, 0}, {13, 5}}, // j 1 + {{2, 1}, {1, 0}, {7, 0}} // j 2 + }}, + {// face 3 + { + // i 0 + {{26, 0}, {42, 0}, {58, 0}}, // j 0 + {{29, 0}, {43, 0}, {62, 3}}, // j 1 + {{38, 1}, {47, 3}, {64, 3}} // j 2 + }, + { + // i 1 + {{12, 0}, {28, 5}, {44, 5}}, // j 0 + {{13, 0}, {26, 0}, {42, 0}}, // j 1 + {{21, 1}, {29, 0}, {43, 0}} // j 2 + }, + { + // i 2 + {{4, 3}, {15, 5}, {31, 5}}, // j 0 + {{3, 1}, {12, 0}, {28, 5}}, // j 1 + {{7, 1}, {13, 0}, {26, 0}} // j 2 + }}, + {// face 4 + { + // i 0 + {{31, 0}, {41, 0}, {49, 0}}, // j 0 + {{44, 0}, {53, 0}, {61, 3}}, // j 1 + {{58, 1}, {65, 3}, {75, 3}} // j 2 + }, + { + // i 1 + {{15, 0}, {22, 5}, {33, 5}}, // j 0 + {{28, 0}, {31, 0}, {41, 0}}, // j 1 + {{42, 1}, {44, 0}, {53, 0}} // j 2 + }, + { + // i 2 + {{4, 4}, {8, 5}, {16, 5}}, // j 0 + {{12, 1}, {15, 0}, {22, 5}}, // j 1 + {{26, 1}, {28, 0}, {31, 0}} // j 2 + }}, + {// face 5 + { + // i 0 + {{50, 0}, {48, 0}, {49, 3}}, // j 0 + {{32, 0}, {30, 3}, {33, 3}}, // j 1 + {{24, 3}, {18, 3}, {16, 3}} // j 2 + }, + { + // i 1 + {{70, 0}, {67, 0}, {66, 3}}, // j 0 + {{52, 3}, {50, 0}, {48, 0}}, // j 1 + {{37, 3}, {32, 0}, {30, 3}} // j 2 + }, + { + // i 2 + {{83, 0}, {87, 3}, {85, 3}}, // j 0 + {{74, 3}, {70, 0}, {67, 0}}, // j 1 + {{57, 1}, {52, 3}, {50, 0}} // j 2 + }}, + {// face 6 + { + // i 0 + {{25, 0}, {23, 0}, {24, 3}}, // j 0 + {{17, 0}, {11, 3}, {10, 3}}, // j 1 + {{14, 3}, {6, 3}, {2, 3}} // j 2 + }, + { + // i 1 + {{45, 0}, {39, 0}, {37, 3}}, // j 0 + {{35, 3}, {25, 0}, {23, 0}}, // j 1 + {{27, 3}, {17, 0}, {11, 3}} // j 2 + }, + { + // i 2 + {{63, 0}, {59, 3}, {57, 3}}, // j 0 + {{56, 3}, {45, 0}, {39, 0}}, // j 1 + {{46, 3}, {35, 3}, {25, 0}} // j 2 + }}, + {// face 7 + { + // i 0 + {{36, 0}, {20, 0}, {14, 3}}, // j 0 + {{34, 0}, {19, 3}, {9, 3}}, // j 1 + {{38, 3}, {21, 3}, {7, 3}} // j 2 + }, + { + // i 1 + {{55, 0}, {40, 0}, {27, 3}}, // j 0 + {{54, 3}, {36, 0}, {20, 0}}, // j 1 + {{51, 3}, {34, 0}, {19, 3}} // j 2 + }, + { + // i 2 + {{72, 0}, {60, 3}, {46, 3}}, // j 0 + {{73, 3}, {55, 0}, {40, 0}}, // j 1 + {{71, 3}, {54, 3}, {36, 0}} // j 2 + }}, + {// face 8 + { + // i 0 + {{64, 0}, {47, 0}, {38, 3}}, // j 0 + {{62, 0}, {43, 3}, {29, 3}}, // j 1 + {{58, 3}, {42, 3}, {26, 3}} // j 2 + }, + { + // i 1 + {{84, 0}, {69, 0}, {51, 3}}, // j 0 + {{82, 3}, {64, 0}, {47, 0}}, // j 1 + {{76, 3}, {62, 0}, {43, 3}} // j 2 + }, + { + // i 2 + {{97, 0}, {89, 3}, {71, 3}}, // j 0 + {{98, 3}, {84, 0}, {69, 0}}, // j 1 + {{96, 3}, {82, 3}, {64, 0}} // j 2 + }}, + {// face 9 + { + // i 0 + {{75, 0}, {65, 0}, {58, 3}}, // j 0 + {{61, 0}, {53, 3}, {44, 3}}, // j 1 + {{49, 3}, {41, 3}, {31, 3}} // j 2 + }, + { + // i 1 + {{94, 0}, {86, 0}, {76, 3}}, // j 0 + {{81, 3}, {75, 0}, {65, 0}}, // j 1 + {{66, 3}, {61, 0}, {53, 3}} // j 2 + }, + { + // i 2 + {{107, 0}, {104, 3}, {96, 3}}, // j 0 + {{101, 3}, {94, 0}, {86, 0}}, // j 1 + {{85, 3}, {81, 3}, {75, 0}} // j 2 + }}, + {// face 10 + { + // i 0 + {{57, 0}, {59, 0}, {63, 3}}, // j 0 + {{74, 0}, {78, 3}, {79, 3}}, // j 1 + {{83, 3}, {92, 3}, {95, 3}} // j 2 + }, + { + // i 1 + {{37, 0}, {39, 3}, {45, 3}}, // j 0 + {{52, 0}, {57, 0}, {59, 0}}, // j 1 + {{70, 3}, {74, 0}, {78, 3}} // j 2 + }, + { + // i 2 + {{24, 0}, {23, 3}, {25, 3}}, // j 0 + {{32, 3}, {37, 0}, {39, 3}}, // j 1 + {{50, 3}, {52, 0}, {57, 0}} // j 2 + }}, + {// face 11 + { + // i 0 + {{46, 0}, {60, 0}, {72, 3}}, // j 0 + {{56, 0}, {68, 3}, {80, 3}}, // j 1 + {{63, 3}, {77, 3}, {90, 3}} // j 2 + }, + { + // i 1 + {{27, 0}, {40, 3}, {55, 3}}, // j 0 + {{35, 0}, {46, 0}, {60, 0}}, // j 1 + {{45, 3}, {56, 0}, {68, 3}} // j 2 + }, + { + // i 2 + {{14, 0}, {20, 3}, {36, 3}}, // j 0 + {{17, 3}, {27, 0}, {40, 3}}, // j 1 + {{25, 3}, {35, 0}, {46, 0}} // j 2 + }}, + {// face 12 + { + // i 0 + {{71, 0}, {89, 0}, {97, 3}}, // j 0 + {{73, 0}, {91, 3}, {103, 3}}, // j 1 + {{72, 3}, {88, 3}, {105, 3}} // j 2 + }, + { + // i 1 + {{51, 0}, {69, 3}, {84, 3}}, // j 0 + {{54, 0}, {71, 0}, {89, 0}}, // j 1 + {{55, 3}, {73, 0}, {91, 3}} // j 2 + }, + { + // i 2 + {{38, 0}, {47, 3}, {64, 3}}, // j 0 + {{34, 3}, {51, 0}, {69, 3}}, // j 1 + {{36, 3}, {54, 0}, {71, 0}} // j 2 + }}, + {// face 13 + { + // i 0 + {{96, 0}, {104, 0}, {107, 3}}, // j 0 + {{98, 0}, {110, 3}, {115, 3}}, // j 1 + {{97, 3}, {111, 3}, {119, 3}} // j 2 + }, + { + // i 1 + {{76, 0}, {86, 3}, {94, 3}}, // j 0 + {{82, 0}, {96, 0}, {104, 0}}, // j 1 + {{84, 3}, {98, 0}, {110, 3}} // j 2 + }, + { + // i 2 + {{58, 0}, {65, 3}, {75, 3}}, // j 0 + {{62, 3}, {76, 0}, {86, 3}}, // j 1 + {{64, 3}, {82, 0}, {96, 0}} // j 2 + }}, + {// face 14 + { + // i 0 + {{85, 0}, {87, 0}, {83, 3}}, // j 0 + {{101, 0}, {102, 3}, {100, 3}}, // j 1 + {{107, 3}, {112, 3}, {114, 3}} // j 2 + }, + { + // i 1 + {{66, 0}, {67, 3}, {70, 3}}, // j 0 + {{81, 0}, {85, 0}, {87, 0}}, // j 1 + {{94, 3}, {101, 0}, {102, 3}} // j 2 + }, + { + // i 2 + {{49, 0}, {48, 3}, {50, 3}}, // j 0 + {{61, 3}, {66, 0}, {67, 3}}, // j 1 + {{75, 3}, {81, 0}, {85, 0}} // j 2 + }}, + {// face 15 + { + // i 0 + {{95, 0}, {92, 0}, {83, 0}}, // j 0 + {{79, 0}, {78, 0}, {74, 3}}, // j 1 + {{63, 1}, {59, 3}, {57, 3}} // j 2 + }, + { + // i 1 + {{109, 0}, {108, 0}, {100, 5}}, // j 0 + {{93, 1}, {95, 0}, {92, 0}}, // j 1 + {{77, 1}, {79, 0}, {78, 0}} // j 2 + }, + { + // i 2 + {{117, 4}, {118, 5}, {114, 5}}, // j 0 + {{106, 1}, {109, 0}, {108, 0}}, // j 1 + {{90, 1}, {93, 1}, {95, 0}} // j 2 + }}, + {// face 16 + { + // i 0 + {{90, 0}, {77, 0}, {63, 0}}, // j 0 + {{80, 0}, {68, 0}, {56, 3}}, // j 1 + {{72, 1}, {60, 3}, {46, 3}} // j 2 + }, + { + // i 1 + {{106, 0}, {93, 0}, {79, 5}}, // j 0 + {{99, 1}, {90, 0}, {77, 0}}, // j 1 + {{88, 1}, {80, 0}, {68, 0}} // j 2 + }, + { + // i 2 + {{117, 3}, {109, 5}, {95, 5}}, // j 0 + {{113, 1}, {106, 0}, {93, 0}}, // j 1 + {{105, 1}, {99, 1}, {90, 0}} // j 2 + }}, + {// face 17 + { + // i 0 + {{105, 0}, {88, 0}, {72, 0}}, // j 0 + {{103, 0}, {91, 0}, {73, 3}}, // j 1 + {{97, 1}, {89, 3}, {71, 3}} // j 2 + }, + { + // i 1 + {{113, 0}, {99, 0}, {80, 5}}, // j 0 + {{116, 1}, {105, 0}, {88, 0}}, // j 1 + {{111, 1}, {103, 0}, {91, 0}} // j 2 + }, + { + // i 2 + {{117, 2}, {106, 5}, {90, 5}}, // j 0 + {{121, 1}, {113, 0}, {99, 0}}, // j 1 + {{119, 1}, {116, 1}, {105, 0}} // j 2 + }}, + {// face 18 + { + // i 0 + {{119, 0}, {111, 0}, {97, 0}}, // j 0 + {{115, 0}, {110, 0}, {98, 3}}, // j 1 + {{107, 1}, {104, 3}, {96, 3}} // j 2 + }, + { + // i 1 + {{121, 0}, {116, 0}, {103, 5}}, // j 0 + {{120, 1}, {119, 0}, {111, 0}}, // j 1 + {{112, 1}, {115, 0}, {110, 0}} // j 2 + }, + { + // i 2 + {{117, 1}, {113, 5}, {105, 5}}, // j 0 + {{118, 1}, {121, 0}, {116, 0}}, // j 1 + {{114, 1}, {120, 1}, {119, 0}} // j 2 + }}, + {// face 19 + { + // i 0 + {{114, 0}, {112, 0}, {107, 0}}, // j 0 + {{100, 0}, {102, 0}, {101, 3}}, // j 1 + {{83, 1}, {87, 3}, {85, 3}} // j 2 + }, + { + // i 1 + {{118, 0}, {120, 0}, {115, 5}}, // j 0 + {{108, 1}, {114, 0}, {112, 0}}, // j 1 + {{92, 1}, {100, 0}, {102, 0}} // j 2 + }, + { + // i 2 + {{117, 0}, {121, 5}, {119, 5}}, // j 0 + {{109, 1}, {118, 0}, {120, 0}}, // j 1 + {{95, 1}, {108, 1}, {114, 0}} // j 2 + }}}; + +/** @brief Resolution 0 base cell data table. + * + * For each base cell, gives the "home" face and ijk+ coordinates on that face, + * whether or not the base cell is a pentagon. Additionally, if the base cell + * is a pentagon, the two cw offset rotation adjacent faces are given (-1 + * indicates that no cw offset rotation faces exist for this base cell). + */ +const BaseCellData baseCellData[NUM_BASE_CELLS] = { + + {{1, {1, 0, 0}}, 0, {0, 0}}, // base cell 0 + {{2, {1, 1, 0}}, 0, {0, 0}}, // base cell 1 + {{1, {0, 0, 0}}, 0, {0, 0}}, // base cell 2 + {{2, {1, 0, 0}}, 0, {0, 0}}, // base cell 3 + {{0, {2, 0, 0}}, 1, {-1, -1}}, // base cell 4 + {{1, {1, 1, 0}}, 0, {0, 0}}, // base cell 5 + {{1, {0, 0, 1}}, 0, {0, 0}}, // base cell 6 + {{2, {0, 0, 0}}, 0, {0, 0}}, // base cell 7 + {{0, {1, 0, 0}}, 0, {0, 0}}, // base cell 8 + {{2, {0, 1, 0}}, 0, {0, 0}}, // base cell 9 + {{1, {0, 1, 0}}, 0, {0, 0}}, // base cell 10 + {{1, {0, 1, 1}}, 0, {0, 0}}, // base cell 11 + {{3, {1, 0, 0}}, 0, {0, 0}}, // base cell 12 + {{3, {1, 1, 0}}, 0, {0, 0}}, // base cell 13 + {{11, {2, 0, 0}}, 1, {2, 6}}, // base cell 14 + {{4, {1, 0, 0}}, 0, {0, 0}}, // base cell 15 + {{0, {0, 0, 0}}, 0, {0, 0}}, // base cell 16 + {{6, {0, 1, 0}}, 0, {0, 0}}, // base cell 17 + {{0, {0, 0, 1}}, 0, {0, 0}}, // base cell 18 + {{2, {0, 1, 1}}, 0, {0, 0}}, // base cell 19 + {{7, {0, 0, 1}}, 0, {0, 0}}, // base cell 20 + {{2, {0, 0, 1}}, 0, {0, 0}}, // base cell 21 + {{0, {1, 1, 0}}, 0, {0, 0}}, // base cell 22 + {{6, {0, 0, 1}}, 0, {0, 0}}, // base cell 23 + {{10, {2, 0, 0}}, 1, {1, 5}}, // base cell 24 + {{6, {0, 0, 0}}, 0, {0, 0}}, // base cell 25 + {{3, {0, 0, 0}}, 0, {0, 0}}, // base cell 26 + {{11, {1, 0, 0}}, 0, {0, 0}}, // base cell 27 + {{4, {1, 1, 0}}, 0, {0, 0}}, // base cell 28 + {{3, {0, 1, 0}}, 0, {0, 0}}, // base cell 29 + {{0, {0, 1, 1}}, 0, {0, 0}}, // base cell 30 + {{4, {0, 0, 0}}, 0, {0, 0}}, // base cell 31 + {{5, {0, 1, 0}}, 0, {0, 0}}, // base cell 32 + {{0, {0, 1, 0}}, 0, {0, 0}}, // base cell 33 + {{7, {0, 1, 0}}, 0, {0, 0}}, // base cell 34 + {{11, {1, 1, 0}}, 0, {0, 0}}, // base cell 35 + {{7, {0, 0, 0}}, 0, {0, 0}}, // base cell 36 + {{10, {1, 0, 0}}, 0, {0, 0}}, // base cell 37 + {{12, {2, 0, 0}}, 1, {3, 7}}, // base cell 38 + {{6, {1, 0, 1}}, 0, {0, 0}}, // base cell 39 + {{7, {1, 0, 1}}, 0, {0, 0}}, // base cell 40 + {{4, {0, 0, 1}}, 0, {0, 0}}, // base cell 41 + {{3, {0, 0, 1}}, 0, {0, 0}}, // base cell 42 + {{3, {0, 1, 1}}, 0, {0, 0}}, // base cell 43 + {{4, {0, 1, 0}}, 0, {0, 0}}, // base cell 44 + {{6, {1, 0, 0}}, 0, {0, 0}}, // base cell 45 + {{11, {0, 0, 0}}, 0, {0, 0}}, // base cell 46 + {{8, {0, 0, 1}}, 0, {0, 0}}, // base cell 47 + {{5, {0, 0, 1}}, 0, {0, 0}}, // base cell 48 + {{14, {2, 0, 0}}, 1, {0, 9}}, // base cell 49 + {{5, {0, 0, 0}}, 0, {0, 0}}, // base cell 50 + {{12, {1, 0, 0}}, 0, {0, 0}}, // base cell 51 + {{10, {1, 1, 0}}, 0, {0, 0}}, // base cell 52 + {{4, {0, 1, 1}}, 0, {0, 0}}, // base cell 53 + {{12, {1, 1, 0}}, 0, {0, 0}}, // base cell 54 + {{7, {1, 0, 0}}, 0, {0, 0}}, // base cell 55 + {{11, {0, 1, 0}}, 0, {0, 0}}, // base cell 56 + {{10, {0, 0, 0}}, 0, {0, 0}}, // base cell 57 + {{13, {2, 0, 0}}, 1, {4, 8}}, // base cell 58 + {{10, {0, 0, 1}}, 0, {0, 0}}, // base cell 59 + {{11, {0, 0, 1}}, 0, {0, 0}}, // base cell 60 + {{9, {0, 1, 0}}, 0, {0, 0}}, // base cell 61 + {{8, {0, 1, 0}}, 0, {0, 0}}, // base cell 62 + {{6, {2, 0, 0}}, 1, {11, 15}}, // base cell 63 + {{8, {0, 0, 0}}, 0, {0, 0}}, // base cell 64 + {{9, {0, 0, 1}}, 0, {0, 0}}, // base cell 65 + {{14, {1, 0, 0}}, 0, {0, 0}}, // base cell 66 + {{5, {1, 0, 1}}, 0, {0, 0}}, // base cell 67 + {{16, {0, 1, 1}}, 0, {0, 0}}, // base cell 68 + {{8, {1, 0, 1}}, 0, {0, 0}}, // base cell 69 + {{5, {1, 0, 0}}, 0, {0, 0}}, // base cell 70 + {{12, {0, 0, 0}}, 0, {0, 0}}, // base cell 71 + {{7, {2, 0, 0}}, 1, {12, 16}}, // base cell 72 + {{12, {0, 1, 0}}, 0, {0, 0}}, // base cell 73 + {{10, {0, 1, 0}}, 0, {0, 0}}, // base cell 74 + {{9, {0, 0, 0}}, 0, {0, 0}}, // base cell 75 + {{13, {1, 0, 0}}, 0, {0, 0}}, // base cell 76 + {{16, {0, 0, 1}}, 0, {0, 0}}, // base cell 77 + {{15, {0, 1, 1}}, 0, {0, 0}}, // base cell 78 + {{15, {0, 1, 0}}, 0, {0, 0}}, // base cell 79 + {{16, {0, 1, 0}}, 0, {0, 0}}, // base cell 80 + {{14, {1, 1, 0}}, 0, {0, 0}}, // base cell 81 + {{13, {1, 1, 0}}, 0, {0, 0}}, // base cell 82 + {{5, {2, 0, 0}}, 1, {10, 19}}, // base cell 83 + {{8, {1, 0, 0}}, 0, {0, 0}}, // base cell 84 + {{14, {0, 0, 0}}, 0, {0, 0}}, // base cell 85 + {{9, {1, 0, 1}}, 0, {0, 0}}, // base cell 86 + {{14, {0, 0, 1}}, 0, {0, 0}}, // base cell 87 + {{17, {0, 0, 1}}, 0, {0, 0}}, // base cell 88 + {{12, {0, 0, 1}}, 0, {0, 0}}, // base cell 89 + {{16, {0, 0, 0}}, 0, {0, 0}}, // base cell 90 + {{17, {0, 1, 1}}, 0, {0, 0}}, // base cell 91 + {{15, {0, 0, 1}}, 0, {0, 0}}, // base cell 92 + {{16, {1, 0, 1}}, 0, {0, 0}}, // base cell 93 + {{9, {1, 0, 0}}, 0, {0, 0}}, // base cell 94 + {{15, {0, 0, 0}}, 0, {0, 0}}, // base cell 95 + {{13, {0, 0, 0}}, 0, {0, 0}}, // base cell 96 + {{8, {2, 0, 0}}, 1, {13, 17}}, // base cell 97 + {{13, {0, 1, 0}}, 0, {0, 0}}, // base cell 98 + {{17, {1, 0, 1}}, 0, {0, 0}}, // base cell 99 + {{19, {0, 1, 0}}, 0, {0, 0}}, // base cell 100 + {{14, {0, 1, 0}}, 0, {0, 0}}, // base cell 101 + {{19, {0, 1, 1}}, 0, {0, 0}}, // base cell 102 + {{17, {0, 1, 0}}, 0, {0, 0}}, // base cell 103 + {{13, {0, 0, 1}}, 0, {0, 0}}, // base cell 104 + {{17, {0, 0, 0}}, 0, {0, 0}}, // base cell 105 + {{16, {1, 0, 0}}, 0, {0, 0}}, // base cell 106 + {{9, {2, 0, 0}}, 1, {14, 18}}, // base cell 107 + {{15, {1, 0, 1}}, 0, {0, 0}}, // base cell 108 + {{15, {1, 0, 0}}, 0, {0, 0}}, // base cell 109 + {{18, {0, 1, 1}}, 0, {0, 0}}, // base cell 110 + {{18, {0, 0, 1}}, 0, {0, 0}}, // base cell 111 + {{19, {0, 0, 1}}, 0, {0, 0}}, // base cell 112 + {{17, {1, 0, 0}}, 0, {0, 0}}, // base cell 113 + {{19, {0, 0, 0}}, 0, {0, 0}}, // base cell 114 + {{18, {0, 1, 0}}, 0, {0, 0}}, // base cell 115 + {{18, {1, 0, 1}}, 0, {0, 0}}, // base cell 116 + {{19, {2, 0, 0}}, 1, {-1, -1}}, // base cell 117 + {{19, {1, 0, 0}}, 0, {0, 0}}, // base cell 118 + {{18, {0, 0, 0}}, 0, {0, 0}}, // base cell 119 + {{19, {1, 0, 1}}, 0, {0, 0}}, // base cell 120 + {{18, {1, 0, 0}}, 0, {0, 0}} // base cell 121 +}; + +/** @brief Return whether or not the indicated base cell is a pentagon. */ +int _isBaseCellPentagon(int baseCell) { + if (baseCell < 0 || baseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return false; + } + return baseCellData[baseCell].isPentagon; +} + +/** @brief Return whether the indicated base cell is a pentagon where all + * neighbors are oriented towards it. */ +bool _isBaseCellPolarPentagon(int baseCell) { + return baseCell == 4 || baseCell == 117; +} + +/** @brief Find base cell given FaceIJK. + * + * Given the face number and a resolution 0 ijk+ coordinate in that face's + * face-centered ijk coordinate system, return the base cell located at that + * coordinate. + * + * Valid ijk+ lookup coordinates are from (0, 0, 0) to (2, 2, 2). + */ +int _faceIjkToBaseCell(const FaceIJK *h) { + return faceIjkBaseCells[h->face][h->coord.i][h->coord.j][h->coord.k] + .baseCell; +} + +/** @brief Find base cell given FaceIJK. + * + * Given the face number and a resolution 0 ijk+ coordinate in that face's + * face-centered ijk coordinate system, return the number of 60' ccw rotations + * to rotate into the coordinate system of the base cell at that coordinates. + * + * Valid ijk+ lookup coordinates are from (0, 0, 0) to (2, 2, 2). + */ +int _faceIjkToBaseCellCCWrot60(const FaceIJK *h) { + return faceIjkBaseCells[h->face][h->coord.i][h->coord.j][h->coord.k] + .ccwRot60; +} + +/** @brief Find the FaceIJK given a base cell. + */ +void _baseCellToFaceIjk(int baseCell, FaceIJK *h) { + *h = baseCellData[baseCell].homeFijk; +} + +/** + * @brief Given a base cell and the face it appears on, return + * the number of 60' ccw rotations for that base cell's + * coordinate system. + * @returns The number of rotations, or INVALID_ROTATIONS if the base + * cell is not found on the given face + */ +int _baseCellToCCWrot60(int baseCell, int face) { + if (face < 0 || face > NUM_ICOSA_FACES) return INVALID_ROTATIONS; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 3; k++) { + if (faceIjkBaseCells[face][i][j][k].baseCell == baseCell) { + return faceIjkBaseCells[face][i][j][k].ccwRot60; + } + } + } + } + return INVALID_ROTATIONS; +} + +/** @brief Return whether or not the tested face is a cw offset face. + */ +bool _baseCellIsCwOffset(int baseCell, int testFace) { + return baseCellData[baseCell].cwOffsetPent[0] == testFace || + baseCellData[baseCell].cwOffsetPent[1] == testFace; +} + +/** @brief Return the neighboring base cell in the given direction. + */ +int _getBaseCellNeighbor(int baseCell, Direction dir) { + return baseCellNeighbors[baseCell][dir]; +} + +/** @brief Return the direction from the origin base cell to the neighbor. + * Returns INVALID_DIGIT if the base cells are not neighbors. + */ +Direction _getBaseCellDirection(int originBaseCell, int neighboringBaseCell) { + for (Direction dir = CENTER_DIGIT; dir < NUM_DIGITS; dir++) { + int testBaseCell = _getBaseCellNeighbor(originBaseCell, dir); + if (testBaseCell == neighboringBaseCell) { + return dir; + } + } + return INVALID_DIGIT; +} + +/** + * res0CellCount returns the number of resolution 0 cells + * + * @return int count of resolution 0 cells + */ +int H3_EXPORT(res0CellCount)() { return NUM_BASE_CELLS; } + +/** + * getRes0Cells generates all base cells storing them into the provided + * memory pointer. Buffer must be of size NUM_BASE_CELLS * sizeof(H3Index). + * + * @param out H3Index* the memory to store the resulting base cells in + * @returns E_SUCCESS. + */ +H3Error H3_EXPORT(getRes0Cells)(H3Index *out) { + for (int bc = 0; bc < NUM_BASE_CELLS; bc++) { + H3Index baseCell = H3_INIT; + H3_SET_MODE(baseCell, H3_CELL_MODE); + H3_SET_BASE_CELL(baseCell, bc); + out[bc] = baseCell; + } + return E_SUCCESS; +} diff --git a/src/h3lib/lib/bbox.c b/src/h3lib/lib/bbox.c new file mode 100644 index 0000000..033b02e --- /dev/null +++ b/src/h3lib/lib/bbox.c @@ -0,0 +1,177 @@ +/* + * Copyright 2016-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file bbox.c + * @brief Geographic bounding box functions + */ + +#include "bbox.h" + +#include +#include +#include + +#include "constants.h" +#include "h3Index.h" +#include "latLng.h" + +/** + * Whether the given bounding box crosses the antimeridian + * @param bbox Bounding box to inspect + * @return is transmeridian + */ +bool bboxIsTransmeridian(const BBox *bbox) { return bbox->east < bbox->west; } + +/** + * Get the center of a bounding box + * @param bbox Input bounding box + * @param center Output center coordinate + */ +void bboxCenter(const BBox *bbox, LatLng *center) { + center->lat = (bbox->north + bbox->south) / 2.0; + // If the bbox crosses the antimeridian, shift east 360 degrees + double east = bboxIsTransmeridian(bbox) ? bbox->east + M_2PI : bbox->east; + center->lng = constrainLng((east + bbox->west) / 2.0); +} + +/** + * Whether the bounding box contains a given point + * @param bbox Bounding box + * @param point Point to test + * @return Whether the point is contained + */ +bool bboxContains(const BBox *bbox, const LatLng *point) { + return point->lat >= bbox->south && point->lat <= bbox->north && + (bboxIsTransmeridian(bbox) ? + // transmeridian case + (point->lng >= bbox->west || point->lng <= bbox->east) + : + // standard case + (point->lng >= bbox->west && point->lng <= bbox->east)); +} + +/** + * Whether two bounding boxes are strictly equal + * @param b1 Bounding box 1 + * @param b2 Bounding box 2 + * @return Whether the boxes are equal + */ +bool bboxEquals(const BBox *b1, const BBox *b2) { + return b1->north == b2->north && b1->south == b2->south && + b1->east == b2->east && b1->west == b2->west; +} + +/** + * _hexRadiusKm returns the radius of a given hexagon in Km + * + * @param h3Index the index of the hexagon + * @return the radius of the hexagon in Km + */ +double _hexRadiusKm(H3Index h3Index) { + // There is probably a cheaper way to determine the radius of a + // hexagon, but this way is conceptually simple + LatLng h3Center; + CellBoundary h3Boundary; + H3_EXPORT(cellToLatLng)(h3Index, &h3Center); + H3_EXPORT(cellToBoundary)(h3Index, &h3Boundary); + return H3_EXPORT(greatCircleDistanceKm)(&h3Center, h3Boundary.verts); +} + +/** + * bboxHexEstimate returns an estimated number of hexagons that fit + * within the cartesian-projected bounding box + * + * @param bbox the bounding box to estimate the hexagon fill level + * @param res the resolution of the H3 hexagons to fill the bounding box + * @param out the estimated number of hexagons to fill the bounding box + * @return E_SUCCESS (0) on success, or another value otherwise. + */ +H3Error bboxHexEstimate(const BBox *bbox, int res, int64_t *out) { + // Get the area of the pentagon as the maximally-distorted area possible + H3Index pentagons[12] = {0}; + H3Error pentagonsErr = H3_EXPORT(getPentagons)(res, pentagons); + if (pentagonsErr) { + return pentagonsErr; + } + double pentagonRadiusKm = _hexRadiusKm(pentagons[0]); + // Area of a regular hexagon is 3/2*sqrt(3) * r * r + // The pentagon has the most distortion (smallest edges) and shares its + // edges with hexagons, so the most-distorted hexagons have this area, + // shrunk by 20% off chance that the bounding box perfectly bounds a + // pentagon. + double pentagonAreaKm2 = + 0.8 * (2.59807621135 * pentagonRadiusKm * pentagonRadiusKm); + + // Then get the area of the bounding box of the geoloop in question + LatLng p1, p2; + p1.lat = bbox->north; + p1.lng = bbox->east; + p2.lat = bbox->south; + p2.lng = bbox->west; + double d = H3_EXPORT(greatCircleDistanceKm)(&p1, &p2); + double lngDiff = fabs(p1.lng - p2.lng); + double latDiff = fabs(p1.lat - p2.lat); + if (lngDiff == 0 || latDiff == 0) { + return E_FAILED; + } + double length = fmax(lngDiff, latDiff); + double width = fmin(lngDiff, latDiff); + double ratio = length / width; + // Derived constant based on: https://math.stackexchange.com/a/1921940 + // Clamped to 3 as higher values tend to rapidly drag the estimate to zero. + double a = d * d / fmin(3.0, ratio); + + // Divide the two to get an estimate of the number of hexagons needed + double estimateDouble = ceil(a / pentagonAreaKm2); + if (!isfinite(estimateDouble)) { + return E_FAILED; + } + int64_t estimate = (int64_t)estimateDouble; + if (estimate == 0) estimate = 1; + *out = estimate; + return E_SUCCESS; +} + +/** + * lineHexEstimate returns an estimated number of hexagons that trace + * the cartesian-projected line + * + * @param origin the origin coordinates + * @param destination the destination coordinates + * @param res the resolution of the H3 hexagons to trace the line + * @param out Out parameter for the estimated number of hexagons required to + * trace the line + * @return E_SUCCESS (0) on success or another value otherwise. + */ +H3Error lineHexEstimate(const LatLng *origin, const LatLng *destination, + int res, int64_t *out) { + // Get the area of the pentagon as the maximally-distorted area possible + H3Index pentagons[12] = {0}; + H3Error pentagonsErr = H3_EXPORT(getPentagons)(res, pentagons); + if (pentagonsErr) { + return pentagonsErr; + } + double pentagonRadiusKm = _hexRadiusKm(pentagons[0]); + + double dist = H3_EXPORT(greatCircleDistanceKm)(origin, destination); + double distCeil = ceil(dist / (2 * pentagonRadiusKm)); + if (!isfinite(distCeil)) { + return E_FAILED; + } + int64_t estimate = (int64_t)distCeil; + if (estimate == 0) estimate = 1; + *out = estimate; + return E_SUCCESS; +} diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c new file mode 100644 index 0000000..137084c --- /dev/null +++ b/src/h3lib/lib/coordijk.c @@ -0,0 +1,705 @@ +/* + * Copyright 2016-2018, 2020-2022 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file coordijk.c + * @brief Hex IJK coordinate systems functions including conversions to/from + * lat/lng. + */ + +#include "coordijk.h" + +#include +#include +#include +#include + +#include "constants.h" +#include "h3Assert.h" +#include "latLng.h" +#include "mathExtensions.h" + +#define INT32_MAX_3 (INT32_MAX / 3) + +/** + * Sets an IJK coordinate to the specified component values. + * + * @param ijk The IJK coordinate to set. + * @param i The desired i component value. + * @param j The desired j component value. + * @param k The desired k component value. + */ +void _setIJK(CoordIJK *ijk, int i, int j, int k) { + ijk->i = i; + ijk->j = j; + ijk->k = k; +} + +/** + * Determine the containing hex in ijk+ coordinates for a 2D cartesian + * coordinate vector (from DGGRID). + * + * @param v The 2D cartesian coordinate vector. + * @param h The ijk+ coordinates of the containing hex. + */ +void _hex2dToCoordIJK(const Vec2d *v, CoordIJK *h) { + double a1, a2; + double x1, x2; + int m1, m2; + double r1, r2; + + // quantize into the ij system and then normalize + h->k = 0; + + a1 = fabsl(v->x); + a2 = fabsl(v->y); + + // first do a reverse conversion + x2 = a2 / M_SIN60; + x1 = a1 + x2 / 2.0L; + + // check if we have the center of a hex + m1 = x1; + m2 = x2; + + // otherwise round correctly + r1 = x1 - m1; + r2 = x2 - m2; + + if (r1 < 0.5L) { + if (r1 < 1.0L / 3.0L) { + if (r2 < (1.0L + r1) / 2.0L) { + h->i = m1; + h->j = m2; + } else { + h->i = m1; + h->j = m2 + 1; + } + } else { + if (r2 < (1.0L - r1)) { + h->j = m2; + } else { + h->j = m2 + 1; + } + + if ((1.0L - r1) <= r2 && r2 < (2.0 * r1)) { + h->i = m1 + 1; + } else { + h->i = m1; + } + } + } else { + if (r1 < 2.0L / 3.0L) { + if (r2 < (1.0L - r1)) { + h->j = m2; + } else { + h->j = m2 + 1; + } + + if ((2.0L * r1 - 1.0L) < r2 && r2 < (1.0L - r1)) { + h->i = m1; + } else { + h->i = m1 + 1; + } + } else { + if (r2 < (r1 / 2.0L)) { + h->i = m1 + 1; + h->j = m2; + } else { + h->i = m1 + 1; + h->j = m2 + 1; + } + } + } + + // now fold across the axes if necessary + + if (v->x < 0.0L) { + if ((h->j % 2) == 0) // even + { + long long int axisi = h->j / 2; + long long int diff = h->i - axisi; + h->i = h->i - 2.0 * diff; + } else { + long long int axisi = (h->j + 1) / 2; + long long int diff = h->i - axisi; + h->i = h->i - (2.0 * diff + 1); + } + } + + if (v->y < 0.0L) { + h->i = h->i - (2 * h->j + 1) / 2; + h->j = -1 * h->j; + } + + _ijkNormalize(h); +} + +/** + * Find the center point in 2D cartesian coordinates of a hex. + * + * @param h The ijk coordinates of the hex. + * @param v The 2D cartesian coordinates of the hex center point. + */ +void _ijkToHex2d(const CoordIJK *h, Vec2d *v) { + int i = h->i - h->k; + int j = h->j - h->k; + + v->x = i - 0.5L * j; + v->y = j * M_SQRT3_2; +} + +/** + * Returns whether or not two ijk coordinates contain exactly the same + * component values. + * + * @param c1 The first set of ijk coordinates. + * @param c2 The second set of ijk coordinates. + * @return 1 if the two addresses match, 0 if they do not. + */ +int _ijkMatches(const CoordIJK *c1, const CoordIJK *c2) { + return (c1->i == c2->i && c1->j == c2->j && c1->k == c2->k); +} + +/** + * Add two ijk coordinates. + * + * @param h1 The first set of ijk coordinates. + * @param h2 The second set of ijk coordinates. + * @param sum The sum of the two sets of ijk coordinates. + */ +void _ijkAdd(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *sum) { + sum->i = h1->i + h2->i; + sum->j = h1->j + h2->j; + sum->k = h1->k + h2->k; +} + +/** + * Subtract two ijk coordinates. + * + * @param h1 The first set of ijk coordinates. + * @param h2 The second set of ijk coordinates. + * @param diff The difference of the two sets of ijk coordinates (h1 - h2). + */ +void _ijkSub(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *diff) { + diff->i = h1->i - h2->i; + diff->j = h1->j - h2->j; + diff->k = h1->k - h2->k; +} + +/** + * Uniformly scale ijk coordinates by a scalar. Works in place. + * + * @param c The ijk coordinates to scale. + * @param factor The scaling factor. + */ +void _ijkScale(CoordIJK *c, int factor) { + c->i *= factor; + c->j *= factor; + c->k *= factor; +} + +/** + * Returns true if _ijkNormalize with the given input could have a signed + * integer overflow. Assumes k is set to 0. + */ +bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk) { + // Check for the possibility of overflow + int max, min; + if (ijk->i > ijk->j) { + max = ijk->i; + min = ijk->j; + } else { + max = ijk->j; + min = ijk->i; + } + if (min < 0) { + // Only if the min is less than 0 will the resulting number be larger + // than max. If min is positive, then max is also positive, and a + // positive signed integer minus another positive signed integer will + // not overflow. + if (ADD_INT32S_OVERFLOWS(max, min)) { + // max + min would overflow + return true; + } + if (SUB_INT32S_OVERFLOWS(0, min)) { + // 0 - INT32_MIN would overflow + return true; + } + if (SUB_INT32S_OVERFLOWS(max, min)) { + // max - min would overflow + return true; + } + } + return false; +} + +/** + * Normalizes ijk coordinates by setting the components to the smallest possible + * values. Works in place. + * + * This function does not protect against signed integer overflow. The caller + * must ensure that none of (i - j), (i - k), (j - i), (j - k), (k - i), (k - j) + * will overflow. This function may be changed in the future to make that check + * itself and return an error code. + * + * @param c The ijk coordinates to normalize. + */ +void _ijkNormalize(CoordIJK *c) { + // remove any negative values + if (c->i < 0) { + c->j -= c->i; + c->k -= c->i; + c->i = 0; + } + + if (c->j < 0) { + c->i -= c->j; + c->k -= c->j; + c->j = 0; + } + + if (c->k < 0) { + c->i -= c->k; + c->j -= c->k; + c->k = 0; + } + + // remove the min value if needed + int min = c->i; + if (c->j < min) min = c->j; + if (c->k < min) min = c->k; + if (min > 0) { + c->i -= min; + c->j -= min; + c->k -= min; + } +} + +/** + * Determines the H3 digit corresponding to a unit vector or the zero vector + * in ijk coordinates. + * + * @param ijk The ijk coordinates; must be a unit vector or zero vector. + * @return The H3 digit (0-6) corresponding to the ijk unit vector, zero vector, + * or INVALID_DIGIT (7) on failure. + */ +Direction _unitIjkToDigit(const CoordIJK *ijk) { + CoordIJK c = *ijk; + _ijkNormalize(&c); + + Direction digit = INVALID_DIGIT; + for (Direction i = CENTER_DIGIT; i < NUM_DIGITS; i++) { + if (_ijkMatches(&c, &UNIT_VECS[i])) { + digit = i; + break; + } + } + + return digit; +} + +/** + * Returns non-zero if _upAp7 with the given input could have a signed integer + * overflow. + * + * Assumes ijk is IJK+ coordinates (no negative numbers). + */ +H3Error _upAp7Checked(CoordIJK *ijk) { + // Doesn't need to be checked because i, j, and k must all be non-negative + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + // <0 is checked because the input must all be non-negative, but some + // negative inputs are used in unit tests to exercise the below. + if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { + if (ADD_INT32S_OVERFLOWS(i, i)) { + return E_FAILED; + } + int i2 = i + i; + if (ADD_INT32S_OVERFLOWS(i2, i)) { + return E_FAILED; + } + int i3 = i2 + i; + if (ADD_INT32S_OVERFLOWS(j, j)) { + return E_FAILED; + } + int j2 = j + j; + + if (SUB_INT32S_OVERFLOWS(i3, j)) { + return E_FAILED; + } + if (ADD_INT32S_OVERFLOWS(i, j2)) { + return E_FAILED; + } + } + + // TODO: Do the int math parts here in long double? + ijk->i = (int)lroundl(((i * 3) - j) / 7.0L); + ijk->j = (int)lroundl((i + (j * 2)) / 7.0L); + ijk->k = 0; + + // Expected not to be reachable, because max + min or max - min would need + // to overflow. + if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { + return E_FAILED; + } + _ijkNormalize(ijk); + return E_SUCCESS; +} + +/** + * Returns non-zero if _upAp7r with the given input could have a signed integer + * overflow. + * + * Assumes ijk is IJK+ coordinates (no negative numbers). + */ +H3Error _upAp7rChecked(CoordIJK *ijk) { + // Doesn't need to be checked because i, j, and k must all be non-negative + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + // <0 is checked because the input must all be non-negative, but some + // negative inputs are used in unit tests to exercise the below. + if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { + if (ADD_INT32S_OVERFLOWS(i, i)) { + return E_FAILED; + } + int i2 = i + i; + if (ADD_INT32S_OVERFLOWS(j, j)) { + return E_FAILED; + } + int j2 = j + j; + if (ADD_INT32S_OVERFLOWS(j2, j)) { + return E_FAILED; + } + int j3 = j2 + j; + + if (ADD_INT32S_OVERFLOWS(i2, j)) { + return E_FAILED; + } + if (SUB_INT32S_OVERFLOWS(j3, i)) { + return E_FAILED; + } + } + + // TODO: Do the int math parts here in long double? + ijk->i = (int)lroundl(((i * 2) + j) / 7.0L); + ijk->j = (int)lroundl(((j * 3) - i) / 7.0L); + ijk->k = 0; + + // Expected not to be reachable, because max + min or max - min would need + // to overflow. + if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { + return E_FAILED; + } + _ijkNormalize(ijk); + return E_SUCCESS; +} + +/** + * Find the normalized ijk coordinates of the indexing parent of a cell in a + * counter-clockwise aperture 7 grid. Works in place. + * + * @param ijk The ijk coordinates. + */ +void _upAp7(CoordIJK *ijk) { + // convert to CoordIJ + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + ijk->i = (int)lroundl((3 * i - j) / 7.0L); + ijk->j = (int)lroundl((i + 2 * j) / 7.0L); + ijk->k = 0; + _ijkNormalize(ijk); +} + +/** + * Find the normalized ijk coordinates of the indexing parent of a cell in a + * clockwise aperture 7 grid. Works in place. + * + * @param ijk The ijk coordinates. + */ +void _upAp7r(CoordIJK *ijk) { + // convert to CoordIJ + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + ijk->i = (int)lroundl((2 * i + j) / 7.0L); + ijk->j = (int)lroundl((3 * j - i) / 7.0L); + ijk->k = 0; + _ijkNormalize(ijk); +} + +/** + * Find the normalized ijk coordinates of the hex centered on the indicated + * hex at the next finer aperture 7 counter-clockwise resolution. Works in + * place. + * + * @param ijk The ijk coordinates. + */ +void _downAp7(CoordIJK *ijk) { + // res r unit vectors in res r+1 + CoordIJK iVec = {3, 0, 1}; + CoordIJK jVec = {1, 3, 0}; + CoordIJK kVec = {0, 1, 3}; + + _ijkScale(&iVec, ijk->i); + _ijkScale(&jVec, ijk->j); + _ijkScale(&kVec, ijk->k); + + _ijkAdd(&iVec, &jVec, ijk); + _ijkAdd(ijk, &kVec, ijk); + + _ijkNormalize(ijk); +} + +/** + * Find the normalized ijk coordinates of the hex centered on the indicated + * hex at the next finer aperture 7 clockwise resolution. Works in place. + * + * @param ijk The ijk coordinates. + */ +void _downAp7r(CoordIJK *ijk) { + // res r unit vectors in res r+1 + CoordIJK iVec = {3, 1, 0}; + CoordIJK jVec = {0, 3, 1}; + CoordIJK kVec = {1, 0, 3}; + + _ijkScale(&iVec, ijk->i); + _ijkScale(&jVec, ijk->j); + _ijkScale(&kVec, ijk->k); + + _ijkAdd(&iVec, &jVec, ijk); + _ijkAdd(ijk, &kVec, ijk); + + _ijkNormalize(ijk); +} + +/** + * Find the normalized ijk coordinates of the hex in the specified digit + * direction from the specified ijk coordinates. Works in place. + * + * @param ijk The ijk coordinates. + * @param digit The digit direction from the original ijk coordinates. + */ +void _neighbor(CoordIJK *ijk, Direction digit) { + if (digit > CENTER_DIGIT && digit < NUM_DIGITS) { + _ijkAdd(ijk, &UNIT_VECS[digit], ijk); + _ijkNormalize(ijk); + } +} + +/** + * Rotates ijk coordinates 60 degrees counter-clockwise. Works in place. + * + * @param ijk The ijk coordinates. + */ +void _ijkRotate60ccw(CoordIJK *ijk) { + // unit vector rotations + CoordIJK iVec = {1, 1, 0}; + CoordIJK jVec = {0, 1, 1}; + CoordIJK kVec = {1, 0, 1}; + + _ijkScale(&iVec, ijk->i); + _ijkScale(&jVec, ijk->j); + _ijkScale(&kVec, ijk->k); + + _ijkAdd(&iVec, &jVec, ijk); + _ijkAdd(ijk, &kVec, ijk); + + _ijkNormalize(ijk); +} + +/** + * Rotates ijk coordinates 60 degrees clockwise. Works in place. + * + * @param ijk The ijk coordinates. + */ +void _ijkRotate60cw(CoordIJK *ijk) { + // unit vector rotations + CoordIJK iVec = {1, 0, 1}; + CoordIJK jVec = {1, 1, 0}; + CoordIJK kVec = {0, 1, 1}; + + _ijkScale(&iVec, ijk->i); + _ijkScale(&jVec, ijk->j); + _ijkScale(&kVec, ijk->k); + + _ijkAdd(&iVec, &jVec, ijk); + _ijkAdd(ijk, &kVec, ijk); + + _ijkNormalize(ijk); +} + +/** + * Rotates indexing digit 60 degrees counter-clockwise. Returns result. + * + * @param digit Indexing digit (between 1 and 6 inclusive) + */ +Direction _rotate60ccw(Direction digit) { + switch (digit) { + case K_AXES_DIGIT: + return IK_AXES_DIGIT; + case IK_AXES_DIGIT: + return I_AXES_DIGIT; + case I_AXES_DIGIT: + return IJ_AXES_DIGIT; + case IJ_AXES_DIGIT: + return J_AXES_DIGIT; + case J_AXES_DIGIT: + return JK_AXES_DIGIT; + case JK_AXES_DIGIT: + return K_AXES_DIGIT; + default: + return digit; + } +} + +/** + * Rotates indexing digit 60 degrees clockwise. Returns result. + * + * @param digit Indexing digit (between 1 and 6 inclusive) + */ +Direction _rotate60cw(Direction digit) { + switch (digit) { + case K_AXES_DIGIT: + return JK_AXES_DIGIT; + case JK_AXES_DIGIT: + return J_AXES_DIGIT; + case J_AXES_DIGIT: + return IJ_AXES_DIGIT; + case IJ_AXES_DIGIT: + return I_AXES_DIGIT; + case I_AXES_DIGIT: + return IK_AXES_DIGIT; + case IK_AXES_DIGIT: + return K_AXES_DIGIT; + default: + return digit; + } +} + +/** + * Find the normalized ijk coordinates of the hex centered on the indicated + * hex at the next finer aperture 3 counter-clockwise resolution. Works in + * place. + * + * @param ijk The ijk coordinates. + */ +void _downAp3(CoordIJK *ijk) { + // res r unit vectors in res r+1 + CoordIJK iVec = {2, 0, 1}; + CoordIJK jVec = {1, 2, 0}; + CoordIJK kVec = {0, 1, 2}; + + _ijkScale(&iVec, ijk->i); + _ijkScale(&jVec, ijk->j); + _ijkScale(&kVec, ijk->k); + + _ijkAdd(&iVec, &jVec, ijk); + _ijkAdd(ijk, &kVec, ijk); + + _ijkNormalize(ijk); +} + +/** + * Find the normalized ijk coordinates of the hex centered on the indicated + * hex at the next finer aperture 3 clockwise resolution. Works in place. + * + * @param ijk The ijk coordinates. + */ +void _downAp3r(CoordIJK *ijk) { + // res r unit vectors in res r+1 + CoordIJK iVec = {2, 1, 0}; + CoordIJK jVec = {0, 2, 1}; + CoordIJK kVec = {1, 0, 2}; + + _ijkScale(&iVec, ijk->i); + _ijkScale(&jVec, ijk->j); + _ijkScale(&kVec, ijk->k); + + _ijkAdd(&iVec, &jVec, ijk); + _ijkAdd(ijk, &kVec, ijk); + + _ijkNormalize(ijk); +} + +/** + * Finds the distance between the two coordinates. Returns result. + * + * @param c1 The first set of ijk coordinates. + * @param c2 The second set of ijk coordinates. + */ +int ijkDistance(const CoordIJK *c1, const CoordIJK *c2) { + CoordIJK diff; + _ijkSub(c1, c2, &diff); + _ijkNormalize(&diff); + CoordIJK absDiff = {abs(diff.i), abs(diff.j), abs(diff.k)}; + return MAX(absDiff.i, MAX(absDiff.j, absDiff.k)); +} + +/** + * Transforms coordinates from the IJK+ coordinate system to the IJ coordinate + * system. + * + * @param ijk The input IJK+ coordinates + * @param ij The output IJ coordinates + */ +void ijkToIj(const CoordIJK *ijk, CoordIJ *ij) { + ij->i = ijk->i - ijk->k; + ij->j = ijk->j - ijk->k; +} + +/** + * Transforms coordinates from the IJ coordinate system to the IJK+ coordinate + * system. + * + * @param ij The input IJ coordinates + * @param ijk The output IJK+ coordinates + * @returns E_SUCCESS on success, E_FAILED if signed integer overflow would have + * occurred. + */ +H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk) { + ijk->i = ij->i; + ijk->j = ij->j; + ijk->k = 0; + + if (_ijkNormalizeCouldOverflow(ijk)) { + return E_FAILED; + } + + _ijkNormalize(ijk); + return E_SUCCESS; +} + +/** + * Convert IJK coordinates to cube coordinates, in place + * @param ijk Coordinate to convert + */ +void ijkToCube(CoordIJK *ijk) { + ijk->i = -ijk->i + ijk->k; + ijk->j = ijk->j - ijk->k; + ijk->k = -ijk->i - ijk->j; +} + +/** + * Convert cube coordinates to IJK coordinates, in place + * @param ijk Coordinate to convert + */ +void cubeToIjk(CoordIJK *ijk) { + ijk->i = -ijk->i; + ijk->k = 0; + _ijkNormalize(ijk); +} diff --git a/src/h3lib/lib/directedEdge.c b/src/h3lib/lib/directedEdge.c new file mode 100644 index 0000000..79e8cb0 --- /dev/null +++ b/src/h3lib/lib/directedEdge.c @@ -0,0 +1,294 @@ +/* + * Copyright 2017-2018, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file directedEdge.c + * @brief DirectedEdge functions for manipulating directed edge indexes. + */ + +#include +#include + +#include "algos.h" +#include "constants.h" +#include "coordijk.h" +#include "h3Assert.h" +#include "h3Index.h" +#include "latLng.h" +#include "vertex.h" + +/** + * Returns whether or not the provided H3Indexes are neighbors. + * @param origin The origin H3 index. + * @param destination The destination H3 index. + * @param out Set to 1 if the indexes are neighbors, 0 otherwise + * @return Error code if the origin or destination are invalid or incomparable. + */ +H3Error H3_EXPORT(areNeighborCells)(H3Index origin, H3Index destination, + int *out) { + // Make sure they're hexagon indexes + if (H3_GET_MODE(origin) != H3_CELL_MODE || + H3_GET_MODE(destination) != H3_CELL_MODE) { + return E_CELL_INVALID; + } + + // Hexagons cannot be neighbors with themselves + if (origin == destination) { + *out = 0; + return E_SUCCESS; + } + + // Only hexagons in the same resolution can be neighbors + if (H3_GET_RESOLUTION(origin) != H3_GET_RESOLUTION(destination)) { + return E_RES_MISMATCH; + } + + // H3 Indexes that share the same parent are very likely to be neighbors + // Child 0 is neighbor with all of its parent's 'offspring', the other + // children are neighbors with 3 of the 7 children. So a simple comparison + // of origin and destination parents and then a lookup table of the children + // is a super-cheap way to possibly determine they are neighbors. + int parentRes = H3_GET_RESOLUTION(origin) - 1; + if (parentRes > 0) { + // TODO: Return error codes here + H3Index originParent; + H3_EXPORT(cellToParent)(origin, parentRes, &originParent); + H3Index destinationParent; + H3_EXPORT(cellToParent)(destination, parentRes, &destinationParent); + if (originParent == destinationParent) { + Direction originResDigit = + H3_GET_INDEX_DIGIT(origin, parentRes + 1); + Direction destinationResDigit = + H3_GET_INDEX_DIGIT(destination, parentRes + 1); + if (originResDigit == CENTER_DIGIT || + destinationResDigit == CENTER_DIGIT) { + *out = 1; + return E_SUCCESS; + } + if (originResDigit >= INVALID_DIGIT) { + // Prevent indexing off the end of the array below + return E_CELL_INVALID; + } + if ((originResDigit == K_AXES_DIGIT || + destinationResDigit == K_AXES_DIGIT) && + H3_EXPORT(isPentagon)(originParent)) { + // If these are invalid cells, fail rather than incorrectly + // reporting neighbors. For pentagon cells that are actually + // neighbors across the deleted subsequence, they will fail the + // optimized check below, but they will be accepted by the + // gridDisk check below that. + return E_CELL_INVALID; + } + // These sets are the relevant neighbors in the clockwise + // and counter-clockwise + const Direction neighborSetClockwise[] = { + CENTER_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, J_AXES_DIGIT, + IK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT}; + const Direction neighborSetCounterclockwise[] = { + CENTER_DIGIT, IK_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, + IJ_AXES_DIGIT, I_AXES_DIGIT, J_AXES_DIGIT}; + if (neighborSetClockwise[originResDigit] == destinationResDigit || + neighborSetCounterclockwise[originResDigit] == + destinationResDigit) { + *out = 1; + return E_SUCCESS; + } + } + } + + // Otherwise, we have to determine the neighbor relationship the "hard" way. + H3Index neighborRing[7] = {0}; + H3_EXPORT(gridDisk)(origin, 1, neighborRing); + for (int i = 0; i < 7; i++) { + if (neighborRing[i] == destination) { + *out = 1; + return E_SUCCESS; + } + } + + // Made it here, they definitely aren't neighbors + *out = 0; + return E_SUCCESS; +} + +/** + * Returns a directed edge H3 index based on the provided origin and + * destination + * @param origin The origin H3 hexagon index + * @param destination The destination H3 hexagon index + * @return The directed edge H3Index, or H3_NULL on failure. + */ +H3Error H3_EXPORT(cellsToDirectedEdge)(H3Index origin, H3Index destination, + H3Index *out) { + // Determine the IJK direction from the origin to the destination + Direction direction = directionForNeighbor(origin, destination); + + // The direction will be invalid if the cells are not neighbors + if (direction == INVALID_DIGIT) { + return E_NOT_NEIGHBORS; + } + + // Create the edge index for the neighbor direction + H3Index output = origin; + H3_SET_MODE(output, H3_DIRECTEDEDGE_MODE); + H3_SET_RESERVED_BITS(output, direction); + + *out = output; + return E_SUCCESS; +} + +/** + * Returns the origin hexagon from the directed edge H3Index + * @param edge The edge H3 index + * @return The origin H3 hexagon index, or H3_NULL on failure + */ +H3Error H3_EXPORT(getDirectedEdgeOrigin)(H3Index edge, H3Index *out) { + if (H3_GET_MODE(edge) != H3_DIRECTEDEDGE_MODE) { + return E_DIR_EDGE_INVALID; + } + H3Index origin = edge; + H3_SET_MODE(origin, H3_CELL_MODE); + H3_SET_RESERVED_BITS(origin, 0); + *out = origin; + return E_SUCCESS; +} + +/** + * Returns the destination hexagon from the directed edge H3Index + * @param edge The edge H3 index + * @return The destination H3 hexagon index, or H3_NULL on failure + */ +H3Error H3_EXPORT(getDirectedEdgeDestination)(H3Index edge, H3Index *out) { + Direction direction = H3_GET_RESERVED_BITS(edge); + int rotations = 0; + H3Index origin; + // Note: This call is also checking for H3_DIRECTEDEDGE_MODE + H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin); + if (originResult) { + return originResult; + } + return h3NeighborRotations(origin, direction, &rotations, out); +} + +/** + * Determines if the provided H3Index is a valid directed edge index + * @param edge The directed edge H3Index + * @return 1 if it is a directed edge H3Index, otherwise 0. + */ +int H3_EXPORT(isValidDirectedEdge)(H3Index edge) { + Direction neighborDirection = H3_GET_RESERVED_BITS(edge); + if (neighborDirection <= CENTER_DIGIT || neighborDirection >= NUM_DIGITS) { + return 0; + } + + H3Index origin; + // Note: This call is also checking for H3_DIRECTEDEDGE_MODE + H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin); + if (originResult) { + return 0; + } + if (H3_EXPORT(isPentagon)(origin) && neighborDirection == K_AXES_DIGIT) { + return 0; + } + + return H3_EXPORT(isValidCell)(origin); +} + +/** + * Returns the origin, destination pair of hexagon IDs for the given edge ID + * @param edge The directed edge H3Index + * @param originDestination Pointer to memory to store origin and destination + * IDs + */ +H3Error H3_EXPORT(directedEdgeToCells)(H3Index edge, + H3Index *originDestination) { + H3Error originResult = + H3_EXPORT(getDirectedEdgeOrigin)(edge, &originDestination[0]); + if (originResult) { + return originResult; + } + H3Error destinationResult = + H3_EXPORT(getDirectedEdgeDestination)(edge, &originDestination[1]); + if (destinationResult) { + return destinationResult; + } + return E_SUCCESS; +} + +/** + * Provides all of the directed edges from the current H3Index. + * @param origin The origin hexagon H3Index to find edges for. + * @param edges The memory to store all of the edges inside. + */ +H3Error H3_EXPORT(originToDirectedEdges)(H3Index origin, H3Index *edges) { + // Determine if the origin is a pentagon and special treatment needed. + int isPent = H3_EXPORT(isPentagon)(origin); + + // This is actually quite simple. Just modify the bits of the origin + // slightly for each direction, except the 'k' direction in pentagons, + // which is zeroed. + for (int i = 0; i < 6; i++) { + if (isPent && i == 0) { + edges[i] = H3_NULL; + } else { + edges[i] = origin; + H3_SET_MODE(edges[i], H3_DIRECTEDEDGE_MODE); + H3_SET_RESERVED_BITS(edges[i], i + 1); + } + } + return E_SUCCESS; +} + +/** + * Provides the coordinates defining the directed edge. + * @param edge The directed edge H3Index + * @param cb The cellboundary object to store the edge coordinates. + */ +H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, CellBoundary *cb) { + // Get the origin and neighbor direction from the edge + Direction direction = H3_GET_RESERVED_BITS(edge); + H3Index origin; + H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin); + if (originResult) { + return originResult; + } + + // Get the start vertex for the edge + int startVertex = vertexNumForDirection(origin, direction); + if (startVertex == INVALID_VERTEX_NUM) { + // This is not actually an edge (i.e. no valid direction), + // so return no vertices. + cb->numVerts = 0; + return E_DIR_EDGE_INVALID; + } + + // Get the geo boundary for the appropriate vertexes of the origin. Note + // that while there are always 2 topological vertexes per edge, the + // resulting edge boundary may have an additional distortion vertex if it + // crosses an edge of the icosahedron. + FaceIJK fijk; + H3Error fijkResult = _h3ToFaceIjk(origin, &fijk); + if (NEVER(fijkResult)) { + return fijkResult; + } + int res = H3_GET_RESOLUTION(origin); + int isPent = H3_EXPORT(isPentagon)(origin); + + if (isPent) { + _faceIjkPentToCellBoundary(&fijk, res, startVertex, 2, cb); + } else { + _faceIjkToCellBoundary(&fijk, res, startVertex, 2, cb); + } + return E_SUCCESS; +} diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c new file mode 100644 index 0000000..b46f38e --- /dev/null +++ b/src/h3lib/lib/faceijk.c @@ -0,0 +1,952 @@ +/* + * Copyright 2016-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file faceijk.c + * @brief Functions for working with icosahedral face-centered hex IJK + * coordinate systems. + */ + +#include "faceijk.h" + +#include +#include +#include +#include +#include + +#include "constants.h" +#include "coordijk.h" +#include "h3Index.h" +#include "latLng.h" +#include "vec3d.h" + +/** square root of 7 */ +#define M_SQRT7 2.6457513110645905905016157536392604257102L + +/** @brief icosahedron face centers in lat/lng radians */ +const LatLng faceCenterGeo[NUM_ICOSA_FACES] = { + {0.803582649718989942, 1.248397419617396099}, // face 0 + {1.307747883455638156, 2.536945009877921159}, // face 1 + {1.054751253523952054, -1.347517358900396623}, // face 2 + {0.600191595538186799, -0.450603909469755746}, // face 3 + {0.491715428198773866, 0.401988202911306943}, // face 4 + {0.172745327415618701, 1.678146885280433686}, // face 5 + {0.605929321571350690, 2.953923329812411617}, // face 6 + {0.427370518328979641, -1.888876200336285401}, // face 7 + {-0.079066118549212831, -0.733429513380867741}, // face 8 + {-0.230961644455383637, 0.506495587332349035}, // face 9 + {0.079066118549212831, 2.408163140208925497}, // face 10 + {0.230961644455383637, -2.635097066257444203}, // face 11 + {-0.172745327415618701, -1.463445768309359553}, // face 12 + {-0.605929321571350690, -0.187669323777381622}, // face 13 + {-0.427370518328979641, 1.252716453253507838}, // face 14 + {-0.600191595538186799, 2.690988744120037492}, // face 15 + {-0.491715428198773866, -2.739604450678486295}, // face 16 + {-0.803582649718989942, -1.893195233972397139}, // face 17 + {-1.307747883455638156, -0.604647643711872080}, // face 18 + {-1.054751253523952054, 1.794075294689396615}, // face 19 +}; + +/** @brief icosahedron face centers in x/y/z on the unit sphere */ +static const Vec3d faceCenterPoint[NUM_ICOSA_FACES] = { + {0.2199307791404606, 0.6583691780274996, 0.7198475378926182}, // face 0 + {-0.2139234834501421, 0.1478171829550703, 0.9656017935214205}, // face 1 + {0.1092625278784797, -0.4811951572873210, 0.8697775121287253}, // face 2 + {0.7428567301586791, -0.3593941678278028, 0.5648005936517033}, // face 3 + {0.8112534709140969, 0.3448953237639384, 0.4721387736413930}, // face 4 + {-0.1055498149613921, 0.9794457296411413, 0.1718874610009365}, // face 5 + {-0.8075407579970092, 0.1533552485898818, 0.5695261994882688}, // face 6 + {-0.2846148069787907, -0.8644080972654206, 0.4144792552473539}, // face 7 + {0.7405621473854482, -0.6673299564565524, -0.0789837646326737}, // face 8 + {0.8512303986474293, 0.4722343788582681, -0.2289137388687808}, // face 9 + {-0.7405621473854481, 0.6673299564565524, 0.0789837646326737}, // face 10 + {-0.8512303986474292, -0.4722343788582682, 0.2289137388687808}, // face 11 + {0.1055498149613919, -0.9794457296411413, -0.1718874610009365}, // face 12 + {0.8075407579970092, -0.1533552485898819, -0.5695261994882688}, // face 13 + {0.2846148069787908, 0.8644080972654204, -0.4144792552473539}, // face 14 + {-0.7428567301586791, 0.3593941678278027, -0.5648005936517033}, // face 15 + {-0.8112534709140971, -0.3448953237639382, -0.4721387736413930}, // face 16 + {-0.2199307791404607, -0.6583691780274996, -0.7198475378926182}, // face 17 + {0.2139234834501420, -0.1478171829550704, -0.9656017935214205}, // face 18 + {-0.1092625278784796, 0.4811951572873210, -0.8697775121287253}, // face 19 +}; + +/** @brief icosahedron face ijk axes as azimuth in radians from face center to + * vertex 0/1/2 respectively + */ +static const double faceAxesAzRadsCII[NUM_ICOSA_FACES][3] = { + {5.619958268523939882, 3.525563166130744542, + 1.431168063737548730}, // face 0 + {5.760339081714187279, 3.665943979320991689, + 1.571548876927796127}, // face 1 + {0.780213654393430055, 4.969003859179821079, + 2.874608756786625655}, // face 2 + {0.430469363979999913, 4.619259568766391033, + 2.524864466373195467}, // face 3 + {6.130269123335111400, 4.035874020941915804, + 1.941478918548720291}, // face 4 + {2.692877706530642877, 0.598482604137447119, + 4.787272808923838195}, // face 5 + {2.982963003477243874, 0.888567901084048369, + 5.077358105870439581}, // face 6 + {3.532912002790141181, 1.438516900396945656, + 5.627307105183336758}, // face 7 + {3.494305004259568154, 1.399909901866372864, + 5.588700106652763840}, // face 8 + {3.003214169499538391, 0.908819067106342928, + 5.097609271892733906}, // face 9 + {5.930472956509811562, 3.836077854116615875, + 1.741682751723420374}, // face 10 + {0.138378484090254847, 4.327168688876645809, + 2.232773586483450311}, // face 11 + {0.448714947059150361, 4.637505151845541521, + 2.543110049452346120}, // face 12 + {0.158629650112549365, 4.347419854898940135, + 2.253024752505744869}, // face 13 + {5.891865957979238535, 3.797470855586042958, + 1.703075753192847583}, // face 14 + {2.711123289609793325, 0.616728187216597771, + 4.805518392002988683}, // face 15 + {3.294508837434268316, 1.200113735041072948, + 5.388903939827463911}, // face 16 + {3.804819692245439833, 1.710424589852244509, + 5.899214794638635174}, // face 17 + {3.664438879055192436, 1.570043776661997111, + 5.758833981448388027}, // face 18 + {2.361378999196363184, 0.266983896803167583, + 4.455774101589558636}, // face 19 +}; + +/** @brief Definition of which faces neighbor each other. */ +static const FaceOrientIJK faceNeighbors[NUM_ICOSA_FACES][4] = { + { + // face 0 + {0, {0, 0, 0}, 0}, // central face + {4, {2, 0, 2}, 1}, // ij quadrant + {1, {2, 2, 0}, 5}, // ki quadrant + {5, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 1 + {1, {0, 0, 0}, 0}, // central face + {0, {2, 0, 2}, 1}, // ij quadrant + {2, {2, 2, 0}, 5}, // ki quadrant + {6, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 2 + {2, {0, 0, 0}, 0}, // central face + {1, {2, 0, 2}, 1}, // ij quadrant + {3, {2, 2, 0}, 5}, // ki quadrant + {7, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 3 + {3, {0, 0, 0}, 0}, // central face + {2, {2, 0, 2}, 1}, // ij quadrant + {4, {2, 2, 0}, 5}, // ki quadrant + {8, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 4 + {4, {0, 0, 0}, 0}, // central face + {3, {2, 0, 2}, 1}, // ij quadrant + {0, {2, 2, 0}, 5}, // ki quadrant + {9, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 5 + {5, {0, 0, 0}, 0}, // central face + {10, {2, 2, 0}, 3}, // ij quadrant + {14, {2, 0, 2}, 3}, // ki quadrant + {0, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 6 + {6, {0, 0, 0}, 0}, // central face + {11, {2, 2, 0}, 3}, // ij quadrant + {10, {2, 0, 2}, 3}, // ki quadrant + {1, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 7 + {7, {0, 0, 0}, 0}, // central face + {12, {2, 2, 0}, 3}, // ij quadrant + {11, {2, 0, 2}, 3}, // ki quadrant + {2, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 8 + {8, {0, 0, 0}, 0}, // central face + {13, {2, 2, 0}, 3}, // ij quadrant + {12, {2, 0, 2}, 3}, // ki quadrant + {3, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 9 + {9, {0, 0, 0}, 0}, // central face + {14, {2, 2, 0}, 3}, // ij quadrant + {13, {2, 0, 2}, 3}, // ki quadrant + {4, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 10 + {10, {0, 0, 0}, 0}, // central face + {5, {2, 2, 0}, 3}, // ij quadrant + {6, {2, 0, 2}, 3}, // ki quadrant + {15, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 11 + {11, {0, 0, 0}, 0}, // central face + {6, {2, 2, 0}, 3}, // ij quadrant + {7, {2, 0, 2}, 3}, // ki quadrant + {16, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 12 + {12, {0, 0, 0}, 0}, // central face + {7, {2, 2, 0}, 3}, // ij quadrant + {8, {2, 0, 2}, 3}, // ki quadrant + {17, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 13 + {13, {0, 0, 0}, 0}, // central face + {8, {2, 2, 0}, 3}, // ij quadrant + {9, {2, 0, 2}, 3}, // ki quadrant + {18, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 14 + {14, {0, 0, 0}, 0}, // central face + {9, {2, 2, 0}, 3}, // ij quadrant + {5, {2, 0, 2}, 3}, // ki quadrant + {19, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 15 + {15, {0, 0, 0}, 0}, // central face + {16, {2, 0, 2}, 1}, // ij quadrant + {19, {2, 2, 0}, 5}, // ki quadrant + {10, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 16 + {16, {0, 0, 0}, 0}, // central face + {17, {2, 0, 2}, 1}, // ij quadrant + {15, {2, 2, 0}, 5}, // ki quadrant + {11, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 17 + {17, {0, 0, 0}, 0}, // central face + {18, {2, 0, 2}, 1}, // ij quadrant + {16, {2, 2, 0}, 5}, // ki quadrant + {12, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 18 + {18, {0, 0, 0}, 0}, // central face + {19, {2, 0, 2}, 1}, // ij quadrant + {17, {2, 2, 0}, 5}, // ki quadrant + {13, {0, 2, 2}, 3} // jk quadrant + }, + { + // face 19 + {19, {0, 0, 0}, 0}, // central face + {15, {2, 0, 2}, 1}, // ij quadrant + {18, {2, 2, 0}, 5}, // ki quadrant + {14, {0, 2, 2}, 3} // jk quadrant + }}; + +/** @brief direction from the origin face to the destination face, relative to + * the origin face's coordinate system, or -1 if not adjacent. + */ +static const int adjacentFaceDir[NUM_ICOSA_FACES][NUM_ICOSA_FACES] = { + {0, KI, -1, -1, IJ, JK, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 0 + {IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 1 + {-1, IJ, 0, KI, -1, -1, -1, JK, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 2 + {-1, -1, IJ, 0, KI, -1, -1, -1, JK, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 3 + {KI, -1, -1, IJ, 0, -1, -1, -1, -1, JK, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 4 + {JK, -1, -1, -1, -1, 0, -1, -1, -1, -1, + IJ, -1, -1, -1, KI, -1, -1, -1, -1, -1}, // face 5 + {-1, JK, -1, -1, -1, -1, 0, -1, -1, -1, + KI, IJ, -1, -1, -1, -1, -1, -1, -1, -1}, // face 6 + {-1, -1, JK, -1, -1, -1, -1, 0, -1, -1, + -1, KI, IJ, -1, -1, -1, -1, -1, -1, -1}, // face 7 + {-1, -1, -1, JK, -1, -1, -1, -1, 0, -1, + -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1}, // face 8 + {-1, -1, -1, -1, JK, -1, -1, -1, -1, 0, + -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1}, // face 9 + {-1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, + 0, -1, -1, -1, -1, JK, -1, -1, -1, -1}, // face 10 + {-1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, + -1, 0, -1, -1, -1, -1, JK, -1, -1, -1}, // face 11 + {-1, -1, -1, -1, -1, -1, -1, IJ, KI, -1, + -1, -1, 0, -1, -1, -1, -1, JK, -1, -1}, // face 12 + {-1, -1, -1, -1, -1, -1, -1, -1, IJ, KI, + -1, -1, -1, 0, -1, -1, -1, -1, JK, -1}, // face 13 + {-1, -1, -1, -1, -1, KI, -1, -1, -1, IJ, + -1, -1, -1, -1, 0, -1, -1, -1, -1, JK}, // face 14 + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + JK, -1, -1, -1, -1, 0, IJ, -1, -1, KI}, // face 15 + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, JK, -1, -1, -1, KI, 0, IJ, -1, -1}, // face 16 + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, JK, -1, -1, -1, KI, 0, IJ, -1}, // face 17 + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ}, // face 18 + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, JK, IJ, -1, -1, KI, 0} // face 19 +}; + +/** @brief overage distance table */ +static const int maxDimByCIIres[] = { + 2, // res 0 + -1, // res 1 + 14, // res 2 + -1, // res 3 + 98, // res 4 + -1, // res 5 + 686, // res 6 + -1, // res 7 + 4802, // res 8 + -1, // res 9 + 33614, // res 10 + -1, // res 11 + 235298, // res 12 + -1, // res 13 + 1647086, // res 14 + -1, // res 15 + 11529602 // res 16 +}; + +/** @brief unit scale distance table */ +static const int unitScaleByCIIres[] = { + 1, // res 0 + -1, // res 1 + 7, // res 2 + -1, // res 3 + 49, // res 4 + -1, // res 5 + 343, // res 6 + -1, // res 7 + 2401, // res 8 + -1, // res 9 + 16807, // res 10 + -1, // res 11 + 117649, // res 12 + -1, // res 13 + 823543, // res 14 + -1, // res 15 + 5764801 // res 16 +}; + +/** + * Encodes a coordinate on the sphere to the FaceIJK address of the containing + * cell at the specified resolution. + * + * @param g The spherical coordinates to encode. + * @param res The desired H3 resolution for the encoding. + * @param h The FaceIJK address of the containing cell at resolution res. + */ +void _geoToFaceIjk(const LatLng *g, int res, FaceIJK *h) { + // first convert to hex2d + Vec2d v; + _geoToHex2d(g, res, &h->face, &v); + + // then convert to ijk+ + _hex2dToCoordIJK(&v, &h->coord); +} + +/** + * Encodes a coordinate on the sphere to the corresponding icosahedral face and + * containing 2D hex coordinates relative to that face center. + * + * @param g The spherical coordinates to encode. + * @param res The desired H3 resolution for the encoding. + * @param face The icosahedral face containing the spherical coordinates. + * @param v The 2D hex coordinates of the cell containing the point. + */ +void _geoToHex2d(const LatLng *g, int res, int *face, Vec2d *v) { + // determine the icosahedron face + double sqd; + _geoToClosestFace(g, face, &sqd); + + // cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2 + double r = acos(1 - sqd / 2); + + if (r < EPSILON) { + v->x = v->y = 0.0L; + return; + } + + // now have face and r, now find CCW theta from CII i-axis + double theta = + _posAngleRads(faceAxesAzRadsCII[*face][0] - + _posAngleRads(_geoAzimuthRads(&faceCenterGeo[*face], g))); + + // adjust theta for Class III (odd resolutions) + if (isResolutionClassIII(res)) + theta = _posAngleRads(theta - M_AP7_ROT_RADS); + + // perform gnomonic scaling of r + r = tan(r); + + // scale for current resolution length u + r /= RES0_U_GNOMONIC; + for (int i = 0; i < res; i++) r *= M_SQRT7; + + // we now have (r, theta) in hex2d with theta ccw from x-axes + + // convert to local x,y + v->x = r * cos(theta); + v->y = r * sin(theta); +} + +/** + * Determines the center point in spherical coordinates of a cell given by 2D + * hex coordinates on a particular icosahedral face. + * + * @param v The 2D hex coordinates of the cell. + * @param face The icosahedral face upon which the 2D hex coordinate system is + * centered. + * @param res The H3 resolution of the cell. + * @param substrate Indicates whether or not this grid is actually a substrate + * grid relative to the specified resolution. + * @param g The spherical coordinates of the cell center point. + */ +void _hex2dToGeo(const Vec2d *v, int face, int res, int substrate, LatLng *g) { + // calculate (r, theta) in hex2d + double r = _v2dMag(v); + + if (r < EPSILON) { + *g = faceCenterGeo[face]; + return; + } + + double theta = atan2(v->y, v->x); + + // scale for current resolution length u + for (int i = 0; i < res; i++) r /= M_SQRT7; + + // scale accordingly if this is a substrate grid + if (substrate) { + r /= 3.0; + if (isResolutionClassIII(res)) r /= M_SQRT7; + } + + r *= RES0_U_GNOMONIC; + + // perform inverse gnomonic scaling of r + r = atan(r); + + // adjust theta for Class III + // if a substrate grid, then it's already been adjusted for Class III + if (!substrate && isResolutionClassIII(res)) + theta = _posAngleRads(theta + M_AP7_ROT_RADS); + + // find theta as an azimuth + theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta); + + // now find the point at (r,theta) from the face center + _geoAzDistanceRads(&faceCenterGeo[face], theta, r, g); +} + +/** + * Determines the center point in spherical coordinates of a cell given by + * a FaceIJK address at a specified resolution. + * + * @param h The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. + * @param g The spherical coordinates of the cell center point. + */ +void _faceIjkToGeo(const FaceIJK *h, int res, LatLng *g) { + Vec2d v; + _ijkToHex2d(&h->coord, &v); + _hex2dToGeo(&v, h->face, res, 0, g); +} + +/** + * Generates the cell boundary in spherical coordinates for a pentagonal cell + * given by a FaceIJK address at a specified resolution. + * + * @param h The FaceIJK address of the pentagonal cell. + * @param res The H3 resolution of the cell. + * @param start The first topological vertex to return. + * @param length The number of topological vertexes to return. + * @param g The spherical coordinates of the cell boundary. + */ +void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, + int length, CellBoundary *g) { + int adjRes = res; + FaceIJK centerIJK = *h; + FaceIJK fijkVerts[NUM_PENT_VERTS]; + _faceIjkPentToVerts(¢erIJK, &adjRes, fijkVerts); + + // If we're returning the entire loop, we need one more iteration in case + // of a distortion vertex on the last edge + int additionalIteration = length == NUM_PENT_VERTS ? 1 : 0; + + // convert each vertex to lat/lng + // adjust the face of each vertex as appropriate and introduce + // edge-crossing vertices as needed + g->numVerts = 0; + FaceIJK lastFijk; + for (int vert = start; vert < start + length + additionalIteration; + vert++) { + int v = vert % NUM_PENT_VERTS; + + FaceIJK fijk = fijkVerts[v]; + + _adjustPentVertOverage(&fijk, adjRes); + + // all Class III pentagon edges cross icosa edges + // note that Class II pentagons have vertices on the edge, + // not edge intersections + if (isResolutionClassIII(res) && vert > start) { + // find hex2d of the two vertexes on the last face + + FaceIJK tmpFijk = fijk; + + Vec2d orig2d0; + _ijkToHex2d(&lastFijk.coord, &orig2d0); + + int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; + + const FaceOrientIJK *fijkOrient = + &faceNeighbors[tmpFijk.face][currentToLastDir]; + + tmpFijk.face = fijkOrient->face; + CoordIJK *ijk = &tmpFijk.coord; + + // rotate and translate for adjacent face + for (int i = 0; i < fijkOrient->ccwRot60; i++) _ijkRotate60ccw(ijk); + + CoordIJK transVec = fijkOrient->translate; + _ijkScale(&transVec, unitScaleByCIIres[adjRes] * 3); + _ijkAdd(ijk, &transVec, ijk); + _ijkNormalize(ijk); + + Vec2d orig2d1; + _ijkToHex2d(ijk, &orig2d1); + + // find the appropriate icosa face edge vertexes + int maxDim = maxDimByCIIres[adjRes]; + Vec2d v0 = {3.0 * maxDim, 0.0}; + Vec2d v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; + Vec2d v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; + + Vec2d *edge0; + Vec2d *edge1; + switch (adjacentFaceDir[tmpFijk.face][fijk.face]) { + case IJ: + edge0 = &v0; + edge1 = &v1; + break; + case JK: + edge0 = &v1; + edge1 = &v2; + break; + case KI: + default: + assert(adjacentFaceDir[tmpFijk.face][fijk.face] == KI); + edge0 = &v2; + edge1 = &v0; + break; + } + + // find the intersection and add the lat/lng point to the result + Vec2d inter; + _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); + _hex2dToGeo(&inter, tmpFijk.face, adjRes, 1, + &g->verts[g->numVerts]); + g->numVerts++; + } + + // convert vertex to lat/lng and add to the result + // vert == start + NUM_PENT_VERTS is only used to test for possible + // intersection on last edge + if (vert < start + NUM_PENT_VERTS) { + Vec2d vec; + _ijkToHex2d(&fijk.coord, &vec); + _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); + g->numVerts++; + } + + lastFijk = fijk; + } +} + +/** + * Get the vertices of a pentagon cell as substrate FaceIJK addresses + * + * @param fijk The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. This may be adjusted if + * necessary for the substrate grid resolution. + * @param fijkVerts Output array for the vertices + */ +void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { + // the vertexes of an origin-centered pentagon in a Class II resolution on a + // substrate grid with aperture sequence 33r. The aperture 3 gets us the + // vertices, and the 3r gets us back to Class II. + // vertices listed ccw from the i-axes + CoordIJK vertsCII[NUM_PENT_VERTS] = { + {2, 1, 0}, // 0 + {1, 2, 0}, // 1 + {0, 2, 1}, // 2 + {0, 1, 2}, // 3 + {1, 0, 2}, // 4 + }; + + // the vertexes of an origin-centered pentagon in a Class III resolution on + // a substrate grid with aperture sequence 33r7r. The aperture 3 gets us the + // vertices, and the 3r7r gets us to Class II. vertices listed ccw from the + // i-axes + CoordIJK vertsCIII[NUM_PENT_VERTS] = { + {5, 4, 0}, // 0 + {1, 5, 0}, // 1 + {0, 5, 4}, // 2 + {0, 1, 5}, // 3 + {4, 0, 5}, // 4 + }; + + // get the correct set of substrate vertices for this resolution + CoordIJK *verts; + if (isResolutionClassIII(*res)) + verts = vertsCIII; + else + verts = vertsCII; + + // adjust the center point to be in an aperture 33r substrate grid + // these should be composed for speed + _downAp3(&fijk->coord); + _downAp3r(&fijk->coord); + + // if res is Class III we need to add a cw aperture 7 to get to + // icosahedral Class II + if (isResolutionClassIII(*res)) { + _downAp7r(&fijk->coord); + *res += 1; + } + + // The center point is now in the same substrate grid as the origin + // cell vertices. Add the center point substate coordinates + // to each vertex to translate the vertices to that cell. + for (int v = 0; v < NUM_PENT_VERTS; v++) { + fijkVerts[v].face = fijk->face; + _ijkAdd(&fijk->coord, &verts[v], &fijkVerts[v].coord); + _ijkNormalize(&fijkVerts[v].coord); + } +} + +/** + * Generates the cell boundary in spherical coordinates for a cell given by a + * FaceIJK address at a specified resolution. + * + * @param h The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. + * @param start The first topological vertex to return. + * @param length The number of topological vertexes to return. + * @param g The spherical coordinates of the cell boundary. + */ +void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, + CellBoundary *g) { + int adjRes = res; + FaceIJK centerIJK = *h; + FaceIJK fijkVerts[NUM_HEX_VERTS]; + _faceIjkToVerts(¢erIJK, &adjRes, fijkVerts); + + // If we're returning the entire loop, we need one more iteration in case + // of a distortion vertex on the last edge + int additionalIteration = length == NUM_HEX_VERTS ? 1 : 0; + + // convert each vertex to lat/lng + // adjust the face of each vertex as appropriate and introduce + // edge-crossing vertices as needed + g->numVerts = 0; + int lastFace = -1; + Overage lastOverage = NO_OVERAGE; + for (int vert = start; vert < start + length + additionalIteration; + vert++) { + int v = vert % NUM_HEX_VERTS; + + FaceIJK fijk = fijkVerts[v]; + + const int pentLeading4 = 0; + Overage overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); + + /* + Check for edge-crossing. Each face of the underlying icosahedron is a + different projection plane. So if an edge of the hexagon crosses an + icosahedron edge, an additional vertex must be introduced at that + intersection point. Then each half of the cell edge can be projected + to geographic coordinates using the appropriate icosahedron face + projection. Note that Class II cell edges have vertices on the face + edge, with no edge line intersections. + */ + if (isResolutionClassIII(res) && vert > start && + fijk.face != lastFace && lastOverage != FACE_EDGE) { + // find hex2d of the two vertexes on original face + int lastV = (v + 5) % NUM_HEX_VERTS; + Vec2d orig2d0; + _ijkToHex2d(&fijkVerts[lastV].coord, &orig2d0); + + Vec2d orig2d1; + _ijkToHex2d(&fijkVerts[v].coord, &orig2d1); + + // find the appropriate icosa face edge vertexes + int maxDim = maxDimByCIIres[adjRes]; + Vec2d v0 = {3.0 * maxDim, 0.0}; + Vec2d v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; + Vec2d v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; + + int face2 = ((lastFace == centerIJK.face) ? fijk.face : lastFace); + Vec2d *edge0; + Vec2d *edge1; + switch (adjacentFaceDir[centerIJK.face][face2]) { + case IJ: + edge0 = &v0; + edge1 = &v1; + break; + case JK: + edge0 = &v1; + edge1 = &v2; + break; + // case KI: + default: + assert(adjacentFaceDir[centerIJK.face][face2] == KI); + edge0 = &v2; + edge1 = &v0; + break; + } + + // find the intersection and add the lat/lng point to the result + Vec2d inter; + _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); + /* + If a point of intersection occurs at a hexagon vertex, then each + adjacent hexagon edge will lie completely on a single icosahedron + face, and no additional vertex is required. + */ + bool isIntersectionAtVertex = _v2dAlmostEquals(&orig2d0, &inter) || + _v2dAlmostEquals(&orig2d1, &inter); + if (!isIntersectionAtVertex) { + _hex2dToGeo(&inter, centerIJK.face, adjRes, 1, + &g->verts[g->numVerts]); + g->numVerts++; + } + } + + // convert vertex to lat/lng and add to the result + // vert == start + NUM_HEX_VERTS is only used to test for possible + // intersection on last edge + if (vert < start + NUM_HEX_VERTS) { + Vec2d vec; + _ijkToHex2d(&fijk.coord, &vec); + _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); + g->numVerts++; + } + + lastFace = fijk.face; + lastOverage = overage; + } +} + +/** + * Get the vertices of a cell as substrate FaceIJK addresses + * + * @param fijk The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. This may be adjusted if + * necessary for the substrate grid resolution. + * @param fijkVerts Output array for the vertices + */ +void _faceIjkToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { + // the vertexes of an origin-centered cell in a Class II resolution on a + // substrate grid with aperture sequence 33r. The aperture 3 gets us the + // vertices, and the 3r gets us back to Class II. + // vertices listed ccw from the i-axes + CoordIJK vertsCII[NUM_HEX_VERTS] = { + {2, 1, 0}, // 0 + {1, 2, 0}, // 1 + {0, 2, 1}, // 2 + {0, 1, 2}, // 3 + {1, 0, 2}, // 4 + {2, 0, 1} // 5 + }; + + // the vertexes of an origin-centered cell in a Class III resolution on a + // substrate grid with aperture sequence 33r7r. The aperture 3 gets us the + // vertices, and the 3r7r gets us to Class II. + // vertices listed ccw from the i-axes + CoordIJK vertsCIII[NUM_HEX_VERTS] = { + {5, 4, 0}, // 0 + {1, 5, 0}, // 1 + {0, 5, 4}, // 2 + {0, 1, 5}, // 3 + {4, 0, 5}, // 4 + {5, 0, 1} // 5 + }; + + // get the correct set of substrate vertices for this resolution + CoordIJK *verts; + if (isResolutionClassIII(*res)) + verts = vertsCIII; + else + verts = vertsCII; + + // adjust the center point to be in an aperture 33r substrate grid + // these should be composed for speed + _downAp3(&fijk->coord); + _downAp3r(&fijk->coord); + + // if res is Class III we need to add a cw aperture 7 to get to + // icosahedral Class II + if (isResolutionClassIII(*res)) { + _downAp7r(&fijk->coord); + *res += 1; + } + + // The center point is now in the same substrate grid as the origin + // cell vertices. Add the center point substate coordinates + // to each vertex to translate the vertices to that cell. + for (int v = 0; v < NUM_HEX_VERTS; v++) { + fijkVerts[v].face = fijk->face; + _ijkAdd(&fijk->coord, &verts[v], &fijkVerts[v].coord); + _ijkNormalize(&fijkVerts[v].coord); + } +} + +/** + * Adjusts a FaceIJK address in place so that the resulting cell address is + * relative to the correct icosahedral face. + * + * @param fijk The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. + * @param pentLeading4 Whether or not the cell is a pentagon with a leading + * digit 4. + * @param substrate Whether or not the cell is in a substrate grid. + * @return 0 if on original face (no overage); 1 if on face edge (only occurs + * on substrate grids); 2 if overage on new face interior + */ +Overage _adjustOverageClassII(FaceIJK *fijk, int res, int pentLeading4, + int substrate) { + Overage overage = NO_OVERAGE; + + CoordIJK *ijk = &fijk->coord; + + // get the maximum dimension value; scale if a substrate grid + int maxDim = maxDimByCIIres[res]; + if (substrate) maxDim *= 3; + + // check for overage + if (substrate && ijk->i + ijk->j + ijk->k == maxDim) // on edge + overage = FACE_EDGE; + else if (ijk->i + ijk->j + ijk->k > maxDim) // overage + { + overage = NEW_FACE; + + const FaceOrientIJK *fijkOrient; + if (ijk->k > 0) { + if (ijk->j > 0) // jk "quadrant" + fijkOrient = &faceNeighbors[fijk->face][JK]; + else // ik "quadrant" + { + fijkOrient = &faceNeighbors[fijk->face][KI]; + + // adjust for the pentagonal missing sequence + if (pentLeading4) { + // translate origin to center of pentagon + CoordIJK origin; + _setIJK(&origin, maxDim, 0, 0); + CoordIJK tmp; + _ijkSub(ijk, &origin, &tmp); + // rotate to adjust for the missing sequence + _ijkRotate60cw(&tmp); + // translate the origin back to the center of the triangle + _ijkAdd(&tmp, &origin, ijk); + } + } + } else // ij "quadrant" + fijkOrient = &faceNeighbors[fijk->face][IJ]; + + fijk->face = fijkOrient->face; + + // rotate and translate for adjacent face + for (int i = 0; i < fijkOrient->ccwRot60; i++) _ijkRotate60ccw(ijk); + + CoordIJK transVec = fijkOrient->translate; + int unitScale = unitScaleByCIIres[res]; + if (substrate) unitScale *= 3; + _ijkScale(&transVec, unitScale); + _ijkAdd(ijk, &transVec, ijk); + _ijkNormalize(ijk); + + // overage points on pentagon boundaries can end up on edges + if (substrate && ijk->i + ijk->j + ijk->k == maxDim) // on edge + overage = FACE_EDGE; + } + + return overage; +} + +/** + * Adjusts a FaceIJK address for a pentagon vertex in a substrate grid in + * place so that the resulting cell address is relative to the correct + * icosahedral face. + * + * @param fijk The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. + */ +Overage _adjustPentVertOverage(FaceIJK *fijk, int res) { + int pentLeading4 = 0; + Overage overage; + do { + overage = _adjustOverageClassII(fijk, res, pentLeading4, 1); + } while (overage == NEW_FACE); + return overage; +} + +/** + * Encodes a coordinate on the sphere to the corresponding icosahedral face and + * containing the squared euclidean distance to that face center. + * + * @param g The spherical coordinates to encode. + * @param face The icosahedral face containing the spherical coordinates. + * @param sqd The squared euclidean distance to its icosahedral face center. + */ +void _geoToClosestFace(const LatLng *g, int *face, double *sqd) { + Vec3d v3d; + _geoToVec3d(g, &v3d); + + // determine the icosahedron face + *face = 0; + // The distance between two farthest points is 2.0, therefore the square of + // the distance between two points should always be less or equal than 4.0 . + *sqd = 5.0; + for (int f = 0; f < NUM_ICOSA_FACES; ++f) { + double sqdT = _pointSquareDist(&faceCenterPoint[f], &v3d); + if (sqdT < *sqd) { + *face = f; + *sqd = sqdT; + } + } +} diff --git a/src/h3lib/lib/h3Assert.c b/src/h3lib/lib/h3Assert.c new file mode 100644 index 0000000..7ea614b --- /dev/null +++ b/src/h3lib/lib/h3Assert.c @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file h3Assert.c + * @brief Support code for unit testing + */ + +#include "h3Assert.h" + +#if defined(H3_COVERAGE_TEST) || defined(H3_DEBUG) +/* +** Counter used for coverage testing. Does not come into play for +** release builds. +** +** Access to this global variable is not mutex protected. This might +** result in TSAN warnings. But as the variable does not exist in +** release builds, that should not be a concern. +*/ +unsigned int h3CoverageCounter; +#endif diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c new file mode 100644 index 0000000..25fff0a --- /dev/null +++ b/src/h3lib/lib/h3Index.c @@ -0,0 +1,1211 @@ +/* + * Copyright 2016-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file h3Index.c + * @brief H3Index utility functions + * (see h3api.h for the main library entry functions) + */ +#include "h3Index.h" + +#include +#include +#include +#include + +#include "alloc.h" +#include "baseCells.h" +#include "faceijk.h" +#include "h3Assert.h" +#include "iterators.h" +#include "mathExtensions.h" + +/** + * Returns the H3 resolution of an H3 index. + * @param h The H3 index. + * @return The resolution of the H3 index argument. + */ +int H3_EXPORT(getResolution)(H3Index h) { return H3_GET_RESOLUTION(h); } + +/** + * Returns the H3 base cell "number" of an H3 cell (hexagon or pentagon). + * + * Note: Technically works on H3 edges, but will return base cell of the + * origin cell. + * + * @param h The H3 cell. + * @return The base cell "number" of the H3 cell argument. + */ +int H3_EXPORT(getBaseCellNumber)(H3Index h) { return H3_GET_BASE_CELL(h); } + +/** + * Converts a string representation of an H3 index into an H3 index. + * @param str The string representation of an H3 index. + * @return The H3 index corresponding to the string argument, or H3_NULL if + * invalid. + */ +H3Error H3_EXPORT(stringToH3)(const char *str, H3Index *out) { + H3Index h = H3_NULL; + // If failed, h will be unmodified and we should return H3_NULL anyways. + int read = sscanf(str, "%" PRIx64, &h); + if (read != 1) { + return E_FAILED; + } + *out = h; + return E_SUCCESS; +} + +/** + * Converts an H3 index into a string representation. + * @param h The H3 index to convert. + * @param str The string representation of the H3 index. + * @param sz Size of the buffer `str` + */ +H3Error H3_EXPORT(h3ToString)(H3Index h, char *str, size_t sz) { + // An unsigned 64 bit integer will be expressed in at most + // 16 digits plus 1 for the null terminator. + if (sz < 17) { + // Buffer is potentially not large enough. + return E_MEMORY_BOUNDS; + } + sprintf(str, "%" PRIx64, h); + return E_SUCCESS; +} + +/** + * Returns whether or not an H3 index is a valid cell (hexagon or pentagon). + * @param h The H3 index to validate. + * @return 1 if the H3 index if valid, and 0 if it is not. + */ +int H3_EXPORT(isValidCell)(H3Index h) { + if (H3_GET_HIGH_BIT(h) != 0) return 0; + + if (H3_GET_MODE(h) != H3_CELL_MODE) return 0; + + if (H3_GET_RESERVED_BITS(h) != 0) return 0; + + int baseCell = H3_GET_BASE_CELL(h); + if (NEVER(baseCell < 0) || baseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return 0; + } + + int res = H3_GET_RESOLUTION(h); + if (NEVER(res < 0 || res > MAX_H3_RES)) { + // Resolutions less than zero can not be represented in an index + return 0; + } + + bool foundFirstNonZeroDigit = false; + for (int r = 1; r <= res; r++) { + Direction digit = H3_GET_INDEX_DIGIT(h, r); + + if (!foundFirstNonZeroDigit && digit != CENTER_DIGIT) { + foundFirstNonZeroDigit = true; + if (_isBaseCellPentagon(baseCell) && digit == K_AXES_DIGIT) { + return 0; + } + } + + if (NEVER(digit < CENTER_DIGIT) || digit >= NUM_DIGITS) return 0; + } + + for (int r = res + 1; r <= MAX_H3_RES; r++) { + Direction digit = H3_GET_INDEX_DIGIT(h, r); + if (digit != INVALID_DIGIT) return 0; + } + + return 1; +} + +/** + * Initializes an H3 index. + * @param hp The H3 index to initialize. + * @param res The H3 resolution to initialize the index to. + * @param baseCell The H3 base cell to initialize the index to. + * @param initDigit The H3 digit (0-7) to initialize all of the index digits to. + */ +void setH3Index(H3Index *hp, int res, int baseCell, Direction initDigit) { + H3Index h = H3_INIT; + H3_SET_MODE(h, H3_CELL_MODE); + H3_SET_RESOLUTION(h, res); + H3_SET_BASE_CELL(h, baseCell); + for (int r = 1; r <= res; r++) H3_SET_INDEX_DIGIT(h, r, initDigit); + *hp = h; +} + +/** + * cellToParent produces the parent index for a given H3 index + * + * @param h H3Index to find parent of + * @param parentRes The resolution to switch to (parent, grandparent, etc) + * + * @return H3Index of the parent, or H3_NULL if you actually asked for a child + */ +H3Error H3_EXPORT(cellToParent)(H3Index h, int parentRes, H3Index *out) { + int childRes = H3_GET_RESOLUTION(h); + if (parentRes < 0 || parentRes > MAX_H3_RES) { + return E_RES_DOMAIN; + } else if (parentRes > childRes) { + return E_RES_MISMATCH; + } else if (parentRes == childRes) { + *out = h; + return E_SUCCESS; + } + H3Index parentH = H3_SET_RESOLUTION(h, parentRes); + for (int i = parentRes + 1; i <= childRes; i++) { + H3_SET_INDEX_DIGIT(parentH, i, H3_DIGIT_MASK); + } + *out = parentH; + return E_SUCCESS; +} + +/** + * Determines whether one resolution is a valid child resolution for a cell. + * Each resolution is considered a valid child resolution of itself. + * + * @param h h3Index parent cell + * @param childRes int resolution of the child + * + * @return The validity of the child resolution + */ +static bool _hasChildAtRes(H3Index h, int childRes) { + int parentRes = H3_GET_RESOLUTION(h); + if (childRes < parentRes || childRes > MAX_H3_RES) { + return false; + } + return true; +} + +/** + * cellToChildrenSize returns the exact number of children for a cell at a + * given child resolution. + * + * @param h H3Index to find the number of children of + * @param childRes The child resolution you're interested in + * + * @return int Exact number of children (handles hexagons and pentagons + * correctly) + */ +H3Error H3_EXPORT(cellToChildrenSize)(H3Index h, int childRes, int64_t *out) { + if (!_hasChildAtRes(h, childRes)) return E_RES_DOMAIN; + + int n = childRes - H3_GET_RESOLUTION(h); + + if (H3_EXPORT(isPentagon)(h)) { + *out = 1 + 5 * (_ipow(7, n) - 1) / 6; + } else { + *out = _ipow(7, n); + } + return E_SUCCESS; +} + +/** + * makeDirectChild takes an index and immediately returns the immediate child + * index based on the specified cell number. Bit operations only, could generate + * invalid indexes if not careful (deleted cell under a pentagon). + * + * @param h H3Index to find the direct child of + * @param cellNumber int id of the direct child (0-6) + * + * @return The new H3Index for the child + */ +H3Index makeDirectChild(H3Index h, int cellNumber) { + int childRes = H3_GET_RESOLUTION(h) + 1; + H3Index childH = H3_SET_RESOLUTION(h, childRes); + H3_SET_INDEX_DIGIT(childH, childRes, cellNumber); + return childH; +} + +/** + * cellToChildren takes the given hexagon id and generates all of the children + * at the specified resolution storing them into the provided memory pointer. + * It's assumed that cellToChildrenSize was used to determine the allocation. + * + * @param h H3Index to find the children of + * @param childRes int the child level to produce + * @param children H3Index* the memory to store the resulting addresses in + */ +H3Error H3_EXPORT(cellToChildren)(H3Index h, int childRes, H3Index *children) { + int64_t i = 0; + for (IterCellsChildren iter = iterInitParent(h, childRes); iter.h; + iterStepChild(&iter)) { + children[i] = iter.h; + i++; + } + return E_SUCCESS; +} + +/** + * Zero out index digits from start to end, inclusive. + * No-op if start > end. + */ +H3Index _zeroIndexDigits(H3Index h, int start, int end) { + if (start > end) return h; + + H3Index m = 0; + + m = ~m; + m <<= H3_PER_DIGIT_OFFSET * (end - start + 1); + m = ~m; + m <<= H3_PER_DIGIT_OFFSET * (MAX_H3_RES - end); + m = ~m; + + return h & m; +} + +/** + * cellToCenterChild produces the center child index for a given H3 index at + * the specified resolution + * + * @param h H3Index to find center child of + * @param childRes The resolution to switch to + * @param child H3Index of the center child + * @return 0 (E_SUCCESS) on success + */ +H3Error H3_EXPORT(cellToCenterChild)(H3Index h, int childRes, H3Index *child) { + if (!_hasChildAtRes(h, childRes)) return E_RES_DOMAIN; + + h = _zeroIndexDigits(h, H3_GET_RESOLUTION(h) + 1, childRes); + H3_SET_RESOLUTION(h, childRes); + *child = h; + return E_SUCCESS; +} + +/** + * compactCells takes a set of hexagons all at the same resolution and + * compresses them by pruning full child branches to the parent level. This is + * also done for all parents recursively to get the minimum number of hex + * addresses that perfectly cover the defined space. + * @param h3Set Set of hexagons + * @param compactedSet The output array of compressed hexagons (preallocated) + * @param numHexes The size of the input and output arrays (possible that no + * contiguous regions exist in the set at all and no compression possible) + * @return an error code on bad input data + */ +// todo: update internal implementation for int64_t +H3Error H3_EXPORT(compactCells)(const H3Index *h3Set, H3Index *compactedSet, + const int64_t numHexes) { + if (numHexes == 0) { + return E_SUCCESS; + } + int res = H3_GET_RESOLUTION(h3Set[0]); + if (res == 0) { + // No compaction possible, just copy the set to output + for (int i = 0; i < numHexes; i++) { + compactedSet[i] = h3Set[i]; + } + return E_SUCCESS; + } + H3Index *remainingHexes = H3_MEMORY(malloc)(numHexes * sizeof(H3Index)); + if (!remainingHexes) { + return E_MEMORY_ALLOC; + } + memcpy(remainingHexes, h3Set, numHexes * sizeof(H3Index)); + H3Index *hashSetArray = H3_MEMORY(calloc)(numHexes, sizeof(H3Index)); + if (!hashSetArray) { + H3_MEMORY(free)(remainingHexes); + return E_MEMORY_ALLOC; + } + H3Index *compactedSetOffset = compactedSet; + int numRemainingHexes = numHexes; + while (numRemainingHexes) { + res = H3_GET_RESOLUTION(remainingHexes[0]); + int parentRes = res - 1; + + // If parentRes is less than zero, we've compacted all the way up to the + // base cells. Time to process the remaining cells. + if (parentRes >= 0) { + // Put the parents of the hexagons into the temp array + // via a hashing mechanism, and use the reserved bits + // to track how many times a parent is duplicated + for (int i = 0; i < numRemainingHexes; i++) { + H3Index currIndex = remainingHexes[i]; + // TODO: This case is coverable (reachable by fuzzer) + if (currIndex != 0) { + // If the reserved bits were set by the caller, the + // algorithm below may encounter undefined behavior + // because it expects to have set the reserved bits + // itself. + if (H3_GET_RESERVED_BITS(currIndex) != 0) { + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_CELL_INVALID; + } + + H3Index parent; + H3Error parentError = + H3_EXPORT(cellToParent)(currIndex, parentRes, &parent); + // Should never be reachable as a result of the compact + // algorithm. Can happen if cellToParent errors e.g. + // because of incompatible resolutions. + if (parentError) { + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return parentError; + } + // Modulus hash the parent into the temp array + int loc = (int)(parent % numRemainingHexes); + int loopCount = 0; + while (hashSetArray[loc] != 0) { + if (NEVER(loopCount > numRemainingHexes)) { + // This case should not be possible because at + // most one index is placed into hashSetArray + // per numRemainingHexes. + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_FAILED; + } + H3Index tempIndex = + hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; + if (tempIndex == parent) { + int count = + H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; + int limitCount = 7; + if (H3_EXPORT(isPentagon)( + tempIndex & H3_RESERVED_MASK_NEGATIVE)) { + limitCount--; + } + // One is added to count for this check to match + // one being added to count later in this + // function when checking for all children being + // present. + if (count + 1 > limitCount) { + // Only possible on duplicate input + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_DUPLICATE_INPUT; + } + H3_SET_RESERVED_BITS(parent, count); + hashSetArray[loc] = H3_NULL; + } else { + loc = (loc + 1) % numRemainingHexes; + } + loopCount++; + } + hashSetArray[loc] = parent; + } + } + } + + // Determine which parent hexagons have a complete set + // of children and put them in the compactableHexes array + int compactableCount = 0; + int maxCompactableCount = + numRemainingHexes / 6; // Somehow all pentagons; conservative + if (maxCompactableCount == 0) { + memcpy(compactedSetOffset, remainingHexes, + numRemainingHexes * sizeof(remainingHexes[0])); + break; + } + H3Index *compactableHexes = + H3_MEMORY(calloc)(maxCompactableCount, sizeof(H3Index)); + if (!compactableHexes) { + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_MEMORY_ALLOC; + } + for (int i = 0; i < numRemainingHexes; i++) { + if (hashSetArray[i] == 0) continue; + int count = H3_GET_RESERVED_BITS(hashSetArray[i]) + 1; + // Include the deleted direction for pentagons as implicitly "there" + if (H3_EXPORT(isPentagon)(hashSetArray[i] & + H3_RESERVED_MASK_NEGATIVE)) { + // We need this later on, no need to recalculate + H3_SET_RESERVED_BITS(hashSetArray[i], count); + // Increment count after setting the reserved bits, + // since count is already incremented above, so it + // will be the expected value for a complete hexagon. + count++; + } + if (count == 7) { + // Bingo! Full set! + compactableHexes[compactableCount] = + hashSetArray[i] & H3_RESERVED_MASK_NEGATIVE; + compactableCount++; + } + } + // Uncompactable hexes are immediately copied into the + // output compactedSetOffset + int uncompactableCount = 0; + for (int i = 0; i < numRemainingHexes; i++) { + H3Index currIndex = remainingHexes[i]; + // TODO: This case is coverable (reachable by fuzzer) + if (currIndex != H3_NULL) { + H3Index parent; + H3Error parentError = + H3_EXPORT(cellToParent)(currIndex, parentRes, &parent); + if (parentError) { + H3_MEMORY(free)(compactableHexes); + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return parentError; + } + // Modulus hash the parent into the temp array + // to determine if this index was included in + // the compactableHexes array + int loc = (int)(parent % numRemainingHexes); + int loopCount = 0; + bool isUncompactable = true; + do { + if (NEVER(loopCount > numRemainingHexes)) { + // This case should not be possible because at most one + // index is placed into hashSetArray per input hexagon. + H3_MEMORY(free)(compactableHexes); + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_FAILED; + } + H3Index tempIndex = + hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; + if (tempIndex == parent) { + int count = H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; + if (count == 7) { + isUncompactable = false; + } + break; + } else { + loc = (loc + 1) % numRemainingHexes; + } + loopCount++; + } while (hashSetArray[loc] != parent); + if (isUncompactable) { + compactedSetOffset[uncompactableCount] = remainingHexes[i]; + uncompactableCount++; + } + } + } + // Set up for the next loop + memset(hashSetArray, 0, numHexes * sizeof(H3Index)); + compactedSetOffset += uncompactableCount; + memcpy(remainingHexes, compactableHexes, + compactableCount * sizeof(H3Index)); + numRemainingHexes = compactableCount; + H3_MEMORY(free)(compactableHexes); + } + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_SUCCESS; +} + +/** + * uncompactCells takes a compressed set of cells and expands back to the + * original set of cells. + * + * Skips elements that are H3_NULL (i.e., 0). + * + * @param compactSet Set of compacted cells + * @param numCompact The number of cells in the input compacted set + * @param outSet Output array for decompressed cells (preallocated) + * @param numOut The size of the output array to bound check against + * @param res The H3 resolution to decompress to + * @return An error code if output array is too small or any cell + * is smaller than the output resolution. + */ +H3Error H3_EXPORT(uncompactCells)(const H3Index *compactedSet, + const int64_t numCompacted, H3Index *outSet, + const int64_t numOut, const int res) { + int64_t i = 0; + + for (int64_t j = 0; j < numCompacted; j++) { + if (!_hasChildAtRes(compactedSet[j], res)) return E_RES_MISMATCH; + + for (IterCellsChildren iter = iterInitParent(compactedSet[j], res); + iter.h; i++, iterStepChild(&iter)) { + if (i >= numOut) return E_MEMORY_BOUNDS; // went too far; abort! + outSet[i] = iter.h; + } + } + return E_SUCCESS; +} + +/** + * uncompactCellsSize takes a compacted set of hexagons and provides + * the exact size of the uncompacted set of hexagons. + * + * @param compactedSet Set of hexagons + * @param numHexes The number of hexes in the input set + * @param res The hexagon resolution to decompress to + * @param out The number of hexagons to allocate memory for + * @returns E_SUCCESS on success, or another value on error + */ +H3Error H3_EXPORT(uncompactCellsSize)(const H3Index *compactedSet, + const int64_t numCompacted, const int res, + int64_t *out) { + int64_t numOut = 0; + for (int64_t i = 0; i < numCompacted; i++) { + if (compactedSet[i] == H3_NULL) continue; + + int64_t childrenSize; + H3Error childrenError = + H3_EXPORT(cellToChildrenSize)(compactedSet[i], res, &childrenSize); + if (childrenError) { + // The parent res does not contain `res`. + return E_RES_MISMATCH; + } + numOut += childrenSize; + } + *out = numOut; + return E_SUCCESS; +} + +/** + * isResClassIII takes a hexagon ID and determines if it is in a + * Class III resolution (rotated versus the icosahedron and subject + * to shape distortion adding extra points on icosahedron edges, making + * them not true hexagons). + * @param h The H3Index to check. + * @return Returns 1 if the hexagon is class III, otherwise 0. + */ +int H3_EXPORT(isResClassIII)(H3Index h) { return H3_GET_RESOLUTION(h) % 2; } + +/** + * isPentagon takes an H3Index and determines if it is actually a pentagon. + * @param h The H3Index to check. + * @return Returns 1 if it is a pentagon, otherwise 0. + */ +int H3_EXPORT(isPentagon)(H3Index h) { + return _isBaseCellPentagon(H3_GET_BASE_CELL(h)) && + !_h3LeadingNonZeroDigit(h); +} + +/** + * Returns the highest resolution non-zero digit in an H3Index. + * @param h The H3Index. + * @return The highest resolution non-zero digit in the H3Index. + */ +Direction _h3LeadingNonZeroDigit(H3Index h) { + for (int r = 1; r <= H3_GET_RESOLUTION(h); r++) + if (H3_GET_INDEX_DIGIT(h, r)) return H3_GET_INDEX_DIGIT(h, r); + + // if we're here it's all 0's + return CENTER_DIGIT; +} + +/** + * Rotate an H3Index 60 degrees counter-clockwise about a pentagonal center. + * @param h The H3Index. + */ +H3Index _h3RotatePent60ccw(H3Index h) { + // rotate in place; skips any leading 1 digits (k-axis) + + int foundFirstNonZeroDigit = 0; + for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { + // rotate this digit + H3_SET_INDEX_DIGIT(h, r, _rotate60ccw(H3_GET_INDEX_DIGIT(h, r))); + + // look for the first non-zero digit so we + // can adjust for deleted k-axes sequence + // if necessary + if (!foundFirstNonZeroDigit && H3_GET_INDEX_DIGIT(h, r) != 0) { + foundFirstNonZeroDigit = 1; + + // adjust for deleted k-axes sequence + if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) + h = _h3Rotate60ccw(h); + } + } + return h; +} + +/** + * Rotate an H3Index 60 degrees clockwise about a pentagonal center. + * @param h The H3Index. + */ +H3Index _h3RotatePent60cw(H3Index h) { + // rotate in place; skips any leading 1 digits (k-axis) + + int foundFirstNonZeroDigit = 0; + for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { + // rotate this digit + H3_SET_INDEX_DIGIT(h, r, _rotate60cw(H3_GET_INDEX_DIGIT(h, r))); + + // look for the first non-zero digit so we + // can adjust for deleted k-axes sequence + // if necessary + if (!foundFirstNonZeroDigit && H3_GET_INDEX_DIGIT(h, r) != 0) { + foundFirstNonZeroDigit = 1; + + // adjust for deleted k-axes sequence + if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) h = _h3Rotate60cw(h); + } + } + return h; +} + +/** + * Rotate an H3Index 60 degrees counter-clockwise. + * @param h The H3Index. + */ +H3Index _h3Rotate60ccw(H3Index h) { + for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { + Direction oldDigit = H3_GET_INDEX_DIGIT(h, r); + H3_SET_INDEX_DIGIT(h, r, _rotate60ccw(oldDigit)); + } + + return h; +} + +/** + * Rotate an H3Index 60 degrees clockwise. + * @param h The H3Index. + */ +H3Index _h3Rotate60cw(H3Index h) { + for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { + H3_SET_INDEX_DIGIT(h, r, _rotate60cw(H3_GET_INDEX_DIGIT(h, r))); + } + + return h; +} + +/** + * Convert an FaceIJK address to the corresponding H3Index. + * @param fijk The FaceIJK address. + * @param res The cell resolution. + * @return The encoded H3Index (or H3_NULL on failure). + */ +H3Index _faceIjkToH3(const FaceIJK *fijk, int res) { + // initialize the index + H3Index h = H3_INIT; + H3_SET_MODE(h, H3_CELL_MODE); + H3_SET_RESOLUTION(h, res); + + // check for res 0/base cell + if (res == 0) { + if (fijk->coord.i > MAX_FACE_COORD || fijk->coord.j > MAX_FACE_COORD || + fijk->coord.k > MAX_FACE_COORD) { + // out of range input + return H3_NULL; + } + + H3_SET_BASE_CELL(h, _faceIjkToBaseCell(fijk)); + return h; + } + + // we need to find the correct base cell FaceIJK for this H3 index; + // start with the passed in face and resolution res ijk coordinates + // in that face's coordinate system + FaceIJK fijkBC = *fijk; + + // build the H3Index from finest res up + // adjust r for the fact that the res 0 base cell offsets the indexing + // digits + CoordIJK *ijk = &fijkBC.coord; + for (int r = res - 1; r >= 0; r--) { + CoordIJK lastIJK = *ijk; + CoordIJK lastCenter; + if (isResolutionClassIII(r + 1)) { + // rotate ccw + _upAp7(ijk); + lastCenter = *ijk; + _downAp7(&lastCenter); + } else { + // rotate cw + _upAp7r(ijk); + lastCenter = *ijk; + _downAp7r(&lastCenter); + } + + CoordIJK diff; + _ijkSub(&lastIJK, &lastCenter, &diff); + _ijkNormalize(&diff); + + H3_SET_INDEX_DIGIT(h, r + 1, _unitIjkToDigit(&diff)); + } + + // fijkBC should now hold the IJK of the base cell in the + // coordinate system of the current face + + if (fijkBC.coord.i > MAX_FACE_COORD || fijkBC.coord.j > MAX_FACE_COORD || + fijkBC.coord.k > MAX_FACE_COORD) { + // out of range input + return H3_NULL; + } + + // lookup the correct base cell + int baseCell = _faceIjkToBaseCell(&fijkBC); + H3_SET_BASE_CELL(h, baseCell); + + // rotate if necessary to get canonical base cell orientation + // for this base cell + int numRots = _faceIjkToBaseCellCCWrot60(&fijkBC); + if (_isBaseCellPentagon(baseCell)) { + // force rotation out of missing k-axes sub-sequence + if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) { + // check for a cw/ccw offset face; default is ccw + if (_baseCellIsCwOffset(baseCell, fijkBC.face)) { + h = _h3Rotate60cw(h); + } else { + h = _h3Rotate60ccw(h); + } + } + + for (int i = 0; i < numRots; i++) h = _h3RotatePent60ccw(h); + } else { + for (int i = 0; i < numRots; i++) { + h = _h3Rotate60ccw(h); + } + } + + return h; +} + +/** + * Encodes a coordinate on the sphere to the H3 index of the containing cell at + * the specified resolution. + * + * Returns 0 on invalid input. + * + * @param g The spherical coordinates to encode. + * @param res The desired H3 resolution for the encoding. + * @param out The encoded H3Index. + * @returns E_SUCCESS (0) on success, another value otherwise + */ +H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + if (!isfinite(g->lat) || !isfinite(g->lng)) { + return E_LATLNG_DOMAIN; + } + + FaceIJK fijk; + _geoToFaceIjk(g, res, &fijk); + *out = _faceIjkToH3(&fijk, res); + if (ALWAYS(*out)) { + return E_SUCCESS; + } else { + return E_FAILED; + } +} + +/** + * Convert an H3Index to the FaceIJK address on a specified icosahedral face. + * @param h The H3Index. + * @param fijk The FaceIJK address, initialized with the desired face + * and normalized base cell coordinates. + * @return Returns 1 if the possibility of overage exists, otherwise 0. + */ +int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { + CoordIJK *ijk = &fijk->coord; + int res = H3_GET_RESOLUTION(h); + + // center base cell hierarchy is entirely on this face + int possibleOverage = 1; + if (!_isBaseCellPentagon(H3_GET_BASE_CELL(h)) && + (res == 0 || + (fijk->coord.i == 0 && fijk->coord.j == 0 && fijk->coord.k == 0))) + possibleOverage = 0; + + for (int r = 1; r <= res; r++) { + if (isResolutionClassIII(r)) { + // Class III == rotate ccw + _downAp7(ijk); + } else { + // Class II == rotate cw + _downAp7r(ijk); + } + + _neighbor(ijk, H3_GET_INDEX_DIGIT(h, r)); + } + + return possibleOverage; +} + +/** + * Convert an H3Index to a FaceIJK address. + * @param h The H3Index. + * @param fijk The corresponding FaceIJK address. + */ +H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { + int baseCell = H3_GET_BASE_CELL(h); + if (NEVER(baseCell < 0) || baseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + // To prevent reading uninitialized memory, we zero the output. + fijk->face = 0; + fijk->coord.i = fijk->coord.j = fijk->coord.k = 0; + return E_CELL_INVALID; + } + // adjust for the pentagonal missing sequence; all of sub-sequence 5 needs + // to be adjusted (and some of sub-sequence 4 below) + if (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 5) + h = _h3Rotate60cw(h); + + // start with the "home" face and ijk+ coordinates for the base cell of c + *fijk = baseCellData[baseCell].homeFijk; + if (!_h3ToFaceIjkWithInitializedFijk(h, fijk)) + return E_SUCCESS; // no overage is possible; h lies on this face + + // if we're here we have the potential for an "overage"; i.e., it is + // possible that c lies on an adjacent face + + CoordIJK origIJK = fijk->coord; + + // if we're in Class III, drop into the next finer Class II grid + int res = H3_GET_RESOLUTION(h); + if (isResolutionClassIII(res)) { + // Class III + _downAp7r(&fijk->coord); + res++; + } + + // adjust for overage if needed + // a pentagon base cell with a leading 4 digit requires special handling + int pentLeading4 = + (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 4); + if (_adjustOverageClassII(fijk, res, pentLeading4, 0) != NO_OVERAGE) { + // if the base cell is a pentagon we have the potential for secondary + // overages + if (_isBaseCellPentagon(baseCell)) { + while (_adjustOverageClassII(fijk, res, 0, 0) != NO_OVERAGE) + continue; + } + + if (res != H3_GET_RESOLUTION(h)) _upAp7r(&fijk->coord); + } else if (res != H3_GET_RESOLUTION(h)) { + fijk->coord = origIJK; + } + return E_SUCCESS; +} + +/** + * Determines the spherical coordinates of the center point of an H3 index. + * + * @param h3 The H3 index. + * @param g The spherical coordinates of the H3 cell center. + */ +H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { + FaceIJK fijk; + H3Error e = _h3ToFaceIjk(h3, &fijk); + if (e) { + return e; + } + _faceIjkToGeo(&fijk, H3_GET_RESOLUTION(h3), g); + return E_SUCCESS; +} + +/** + * Determines the cell boundary in spherical coordinates for an H3 index. + * + * @param h3 The H3 index. + * @param cb The boundary of the H3 cell in spherical coordinates. + */ +H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *cb) { + FaceIJK fijk; + H3Error e = _h3ToFaceIjk(h3, &fijk); + if (e) { + return e; + } + if (H3_EXPORT(isPentagon)(h3)) { + _faceIjkPentToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, + NUM_PENT_VERTS, cb); + } else { + _faceIjkToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_HEX_VERTS, + cb); + } + return E_SUCCESS; +} + +/** + * Returns the max number of possible icosahedron faces an H3 index + * may intersect. + * + * @return int count of faces + */ +H3Error H3_EXPORT(maxFaceCount)(H3Index h3, int *out) { + // a pentagon always intersects 5 faces, a hexagon never intersects more + // than 2 (but may only intersect 1) + *out = H3_EXPORT(isPentagon)(h3) ? 5 : 2; + return E_SUCCESS; +} + +/** + * Find all icosahedron faces intersected by a given H3 index, represented + * as integers from 0-19. The array is sparse; since 0 is a valid value, + * invalid array values are represented as -1. It is the responsibility of + * the caller to filter out invalid values. + * + * @param h3 The H3 index + * @param out Output array. Must be of size maxFaceCount(h3). + */ +H3Error H3_EXPORT(getIcosahedronFaces)(H3Index h3, int *out) { + int res = H3_GET_RESOLUTION(h3); + int isPent = H3_EXPORT(isPentagon)(h3); + + // We can't use the vertex-based approach here for class II pentagons, + // because all their vertices are on the icosahedron edges. Their + // direct child pentagons cross the same faces, so use those instead. + if (isPent && !isResolutionClassIII(res)) { + // Note that this would not work for res 15, but this is only run on + // Class II pentagons, it should never be invoked for a res 15 index. + H3Index childPentagon = makeDirectChild(h3, 0); + return H3_EXPORT(getIcosahedronFaces)(childPentagon, out); + } + + // convert to FaceIJK + FaceIJK fijk; + H3Error err = _h3ToFaceIjk(h3, &fijk); + if (err) { + return err; + } + + // Get all vertices as FaceIJK addresses. For simplicity, always + // initialize the array with 6 verts, ignoring the last one for pentagons + FaceIJK fijkVerts[NUM_HEX_VERTS]; + int vertexCount; + + if (isPent) { + vertexCount = NUM_PENT_VERTS; + _faceIjkPentToVerts(&fijk, &res, fijkVerts); + } else { + vertexCount = NUM_HEX_VERTS; + _faceIjkToVerts(&fijk, &res, fijkVerts); + } + + // We may not use all of the slots in the output array, + // so fill with invalid values to indicate unused slots + int faceCount; + H3Error maxFaceCountError = H3_EXPORT(maxFaceCount)(h3, &faceCount); + if (NEVER(maxFaceCountError != E_SUCCESS)) { + return maxFaceCountError; + } + for (int i = 0; i < faceCount; i++) { + out[i] = INVALID_FACE; + } + + // add each vertex face, using the output array as a hash set + for (int i = 0; i < vertexCount; i++) { + FaceIJK *vert = &fijkVerts[i]; + + // Adjust overage, determining whether this vertex is + // on another face + if (isPent) { + _adjustPentVertOverage(vert, res); + } else { + _adjustOverageClassII(vert, res, 0, 1); + } + + // Save the face to the output array + int face = vert->face; + int pos = 0; + // Find the first empty output position, or the first position + // matching the current face + while (out[pos] != INVALID_FACE && out[pos] != face) { + pos++; + if (pos >= faceCount) { + // Mismatch between the heuristic used in maxFaceCount and + // calculation here - indicates an invalid index. + return E_FAILED; + } + } + out[pos] = face; + } + return E_SUCCESS; +} + +/** + * pentagonCount returns the number of pentagons (same at any resolution) + * + * @return int count of pentagon indexes + */ +int H3_EXPORT(pentagonCount)() { return NUM_PENTAGONS; } + +/** + * Generates all pentagons at the specified resolution + * + * @param res The resolution to produce pentagons at. + * @param out Output array. Must be of size pentagonCount(). + */ +H3Error H3_EXPORT(getPentagons)(int res, H3Index *out) { + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + int i = 0; + for (int bc = 0; bc < NUM_BASE_CELLS; bc++) { + if (_isBaseCellPentagon(bc)) { + H3Index pentagon; + setH3Index(&pentagon, res, bc, 0); + out[i++] = pentagon; + } + } + return E_SUCCESS; +} + +/** + * Returns whether or not a resolution is a Class III grid. Note that odd + * resolutions are Class III and even resolutions are Class II. + * @param res The H3 resolution. + * @return 1 if the resolution is a Class III grid, and 0 if the resolution is + * a Class II grid. + */ +int isResolutionClassIII(int res) { return res % 2; } + +/** + * Validate a child position in the context of a given parent, returning + * an error if validation fails. + */ +static H3Error validateChildPos(int64_t childPos, H3Index parent, + int childRes) { + int64_t maxChildCount; + H3Error sizeError = + H3_EXPORT(cellToChildrenSize)(parent, childRes, &maxChildCount); + if (NEVER(sizeError)) { + return sizeError; + } + if (childPos < 0 || childPos >= maxChildCount) { + return E_DOMAIN; + } + return E_SUCCESS; +} + +/** + * Returns the position of the cell within an ordered list of all children of + * the cell's parent at the specified resolution + */ +H3Error H3_EXPORT(cellToChildPos)(H3Index child, int parentRes, int64_t *out) { + int childRes = H3_GET_RESOLUTION(child); + // Get the parent at res. This will catch any resolution errors + H3Index originalParent; + H3Error parentError = + H3_EXPORT(cellToParent(child, parentRes, &originalParent)); + if (parentError) { + return parentError; + } + + // Define the initial parent. Note that these variables are reassigned + // within the loop. + H3Index parent = originalParent; + int parentIsPentagon = H3_EXPORT(isPentagon)(parent); + + // Walk up the resolution digits, incrementing the index + *out = 0; + if (parentIsPentagon) { + // Pentagon logic. Pentagon parents skip the 1 digit, so the offsets are + // different from hexagons + for (int res = childRes; res > parentRes; res--) { + H3Error parentError = + H3_EXPORT(cellToParent(child, res - 1, &parent)); + if (NEVER(parentError)) { + return parentError; + } + + parentIsPentagon = H3_EXPORT(isPentagon)(parent); + int rawDigit = H3_GET_INDEX_DIGIT(child, res); + // Validate the digit before proceeding + if (rawDigit == INVALID_DIGIT || + (parentIsPentagon && rawDigit == K_AXES_DIGIT)) { + return E_CELL_INVALID; + } + int digit = + parentIsPentagon && rawDigit > 0 ? rawDigit - 1 : rawDigit; + if (digit != CENTER_DIGIT) { + int64_t hexChildCount = _ipow(7, childRes - res); + // The offset for the 0-digit slot depends on whether the + // current index is the child of a pentagon. If so, the offset + // is based on the count of pentagon children, otherwise, + // hexagon children. + *out += (parentIsPentagon + ? // pentagon children. See the explanation + // for getNumCells in h3api.h.in + 1 + (5 * (hexChildCount - 1)) / 6 + : // one hexagon's children + hexChildCount) + + // the other hexagon children + (digit - 1) * hexChildCount; + } + } + } else { + // Hexagon logic. Offsets are simple powers of 7 + for (int res = childRes; res > parentRes; res--) { + int digit = H3_GET_INDEX_DIGIT(child, res); + if (digit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + *out += digit * _ipow(7, childRes - res); + } + } + + if (NEVER(validateChildPos(*out, originalParent, childRes))) { + // This is the result of an internal error, so return E_FAILED + // instead of the validation error + return E_FAILED; + } + + return E_SUCCESS; +} + +/** + * Returns the child cell at a given position within an ordered list of all + * children at the specified resolution */ +H3Error H3_EXPORT(childPosToCell)(int64_t childPos, H3Index parent, + int childRes, H3Index *child) { + // Validate resolution + if (childRes < 0 || childRes > MAX_H3_RES) { + return E_RES_DOMAIN; + } + // Validate parent resolution + int parentRes = H3_GET_RESOLUTION(parent); + if (childRes < parentRes) { + return E_RES_MISMATCH; + } + // Validate child pos + H3Error childPosErr = validateChildPos(childPos, parent, childRes); + if (childPosErr) { + return childPosErr; + } + + int resOffset = childRes - parentRes; + + *child = parent; + int64_t idx = childPos; + + H3_SET_RESOLUTION(*child, childRes); + + if (H3_EXPORT(isPentagon)(parent)) { + // Pentagon tile logic. Pentagon tiles skip the 1 digit, so the offsets + // are different + bool inPent = true; + for (int res = 1; res <= resOffset; res++) { + int64_t resWidth = _ipow(7, resOffset - res); + if (inPent) { + // While we are inside a parent pentagon, we need to check if + // this cell is a pentagon, and if not, we need to offset its + // digit to account for the skipped direction + int64_t pentWidth = 1 + (5 * (resWidth - 1)) / 6; + if (idx < pentWidth) { + H3_SET_INDEX_DIGIT(*child, parentRes + res, 0); + } else { + idx -= pentWidth; + inPent = false; + H3_SET_INDEX_DIGIT(*child, parentRes + res, + (idx / resWidth) + 2); + idx %= resWidth; + } + } else { + // We're no longer inside a pentagon, continue as for hex + H3_SET_INDEX_DIGIT(*child, parentRes + res, idx / resWidth); + idx %= resWidth; + } + } + } else { + // Hexagon tile logic. Offsets are simple powers of 7 + for (int res = 1; res <= resOffset; res++) { + int64_t resWidth = _ipow(7, resOffset - res); + H3_SET_INDEX_DIGIT(*child, parentRes + res, idx / resWidth); + idx %= resWidth; + } + } + + return E_SUCCESS; +} diff --git a/src/h3lib/lib/iterators.c b/src/h3lib/lib/iterators.c new file mode 100644 index 0000000..5cbe57f --- /dev/null +++ b/src/h3lib/lib/iterators.c @@ -0,0 +1,324 @@ +/* + * Copyright 2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @file IterCellsChildren.c + * @brief Iterator structs and functions for the children of a cell, + * or cells at a given resolution. + */ + +#include "iterators.h" + +#include "h3Index.h" + +// extract the `res` digit (0--7) of the current cell +static int _getResDigit(IterCellsChildren *it, int res) { + return H3_GET_INDEX_DIGIT(it->h, res); +} + +// increment the digit (0--7) at location `res` +// H3_PER_DIGIT_OFFSET == 3 +static void _incrementResDigit(IterCellsChildren *it, int res) { + H3Index val = 1; + val <<= H3_PER_DIGIT_OFFSET * (MAX_H3_RES - res); + it->h += val; +} + +/** + * Create a fully nulled-out child iterator for when an iterator is exhausted. + * This helps minimize the chance that a user will depend on the iterator + * internal state after it's exhausted, like the child resolution, for + * example. + */ +static IterCellsChildren _null_iter() { + return (IterCellsChildren){ + .h = H3_NULL, ._parentRes = -1, ._skipDigit = -1}; +} + +/** + +## Logic for iterating through the children of a cell + +We'll describe the logic for .... + +- normal (non pentagon iteration) +- pentagon iteration. define "pentagon digit" + + +### Cell Index Component Diagrams + +The lower 56 bits of an H3 Cell Index describe the following index components: + +- the cell resolution (4 bits) +- the base cell number (7 bits) +- the child cell digit for each resolution from 1 to 15 (3*15 = 45 bits) + +These are the bits we'll be focused on when iterating through child cells. +To help describe the iteration logic, we'll use diagrams displaying the +(decimal) values for each component like: + + child digit for resolution 2 + / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | ... | +|-----|-------------|---|---|---|---|---|---|-----| +| 9 | 17 | 5 | 3 | 0 | 6 | 2 | 1 | ... | + + +### Iteration through children of a hexagon (but not a pentagon) + +Iteration through the children of a *hexagon* (but not a pentagon) +simply involves iterating through all the children values (0--6) +for each child digit (up to the child's resolution). + +For example, suppose a resolution 3 hexagon index has the following +components: + parent resolution + / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | ... | +|-----|-------------|---|---|---|---|---|---|-----| +| 3 | 17 | 3 | 5 | 1 | 7 | 7 | 7 | ... | + +The iteration through all children of resolution 6 would look like: + + + parent res child res + / / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... | +|-----|-------------|---|---|---|---|---|---|---|---|-----| +| 6 | 17 | 3 | 5 | 1 | 0 | 0 | 0 | 7 | 7 | ... | +| 6 | 17 | 3 | 5 | 1 | 0 | 0 | 1 | 7 | 7 | ... | +| ... | | | | | | | | | | | +| 6 | 17 | 3 | 5 | 1 | 0 | 0 | 6 | 7 | 7 | ... | +| 6 | 17 | 3 | 5 | 1 | 0 | 1 | 0 | 7 | 7 | ... | +| 6 | 17 | 3 | 5 | 1 | 0 | 1 | 1 | 7 | 7 | ... | +| ... | | | | | | | | | | | +| 6 | 17 | 3 | 5 | 1 | 6 | 6 | 6 | 7 | 7 | ... | + + +### Step sequence on a *pentagon* cell + +Pentagon cells have a base cell number (e.g., 97) corresponding to a +resolution 0 pentagon, and have all zeros from digit 1 to the digit +corresponding to the cell's resolution. +(We'll drop the ellipses from now on, knowing that digits should contain +7's beyond the cell resolution.) + + parent res child res + / / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 0 | 0 | + +Iteration through children of a *pentagon* is almost the same +as *hexagon* iteration, except that we skip the *first* 1 value +that appears in the "skip digit". This corresponds to the fact +that a pentagon only has 6 children, which are denoted with +the numbers {0,2,3,4,5,6}. + +The skip digit starts at the child resolution position. +When iterating through children more than one resolution below +the parent, we move the skip digit to the left +(up to the next coarser resolution) each time we skip the 1 value +in that digit. + +Iteration would start like: + + parent res child res + / / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 0 | 0 | + \ + skip digit + +Noticing we skip the 1 value and move the skip digit, +the next iterate would be: + + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 0 | 2 | + \ + skip digit + +Iteration continues normally until we get to: + + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 0 | 6 | + \ + skip digit + +which is followed by (skipping the 1): + + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 2 | 0 | + \ + skip digit + +For the next iterate, we won't skip the `1` in the previous digit +because it is no longer the skip digit: + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 2 | 1 | + \ + skip digit + +Iteration continues normally until we're right before the next skip +digit: + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 6 | 6 | + \ + skip digit + +Which is followed by + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 2 | 0 | 0 | + \ + skip digit + +and so on. + + */ + +/** + * Initialize a IterCellsChildren struct representing the sequence giving + * the children of cell `h` at resolution `childRes`. + * + * At any point in the iteration, starting once + * the struct is initialized, IterCellsChildren.h gives the current child. + * + * Also, IterCellsChildren.h == H3_NULL when all the children have been iterated + * through, or if the input to `iterInitParent` was invalid. + */ +IterCellsChildren iterInitParent(H3Index h, int childRes) { + IterCellsChildren it; + + it._parentRes = H3_GET_RESOLUTION(h); + + if (childRes < it._parentRes || childRes > MAX_H3_RES || h == H3_NULL) { + return _null_iter(); + } + + it.h = _zeroIndexDigits(h, it._parentRes + 1, childRes); + H3_SET_RESOLUTION(it.h, childRes); + + if (H3_EXPORT(isPentagon)(it.h)) { + // The skip digit skips `1` for pentagons. + // The "_skipDigit" moves to the left as we count up from the + // child resolution to the parent resolution. + it._skipDigit = childRes; + } else { + // if not a pentagon, we can ignore "skip digit" logic + it._skipDigit = -1; + } + + return it; +} + +/** + * Step a IterCellsChildren to the next child cell. + * When the iteration is over, IterCellsChildren.h will be H3_NULL. + * Handles iterating through hexagon and pentagon cells. + */ +void iterStepChild(IterCellsChildren *it) { + // once h == H3_NULL, the iterator returns an infinite sequence of H3_NULL + if (it->h == H3_NULL) return; + + int childRes = H3_GET_RESOLUTION(it->h); + + _incrementResDigit(it, childRes); + + for (int i = childRes; i >= it->_parentRes; i--) { + if (i == it->_parentRes) { + // if we're modifying the parent resolution digit, then we're done + *it = _null_iter(); + return; + } + + // PENTAGON_SKIPPED_DIGIT == 1 + if (i == it->_skipDigit && + _getResDigit(it, i) == PENTAGON_SKIPPED_DIGIT) { + // Then we are iterating through the children of a pentagon cell. + // All children of a pentagon have the property that the first + // nonzero digit between the parent and child resolutions is + // not 1. + // I.e., we never see a sequence like 00001. + // Thus, we skip the `1` in this digit. + _incrementResDigit(it, i); + it->_skipDigit -= 1; + return; + } + + // INVALID_DIGIT == 7 + if (_getResDigit(it, i) == INVALID_DIGIT) { + _incrementResDigit( + it, i); // zeros out it[i] and increments it[i-1] by 1 + } else { + break; + } + } +} + +// create iterator for children of base cell at given resolution +IterCellsChildren iterInitBaseCellNum(int baseCellNum, int childRes) { + if (baseCellNum < 0 || baseCellNum >= NUM_BASE_CELLS || childRes < 0 || + childRes > MAX_H3_RES) { + return _null_iter(); + } + + H3Index baseCell; + setH3Index(&baseCell, 0, baseCellNum, 0); + + return iterInitParent(baseCell, childRes); +} + +// create iterator for all cells at given resolution +IterCellsResolution iterInitRes(int res) { + IterCellsChildren itC = iterInitBaseCellNum(0, res); + + IterCellsResolution itR = { + .h = itC.h, ._baseCellNum = 0, ._res = res, ._itC = itC}; + + return itR; +} + +void iterStepRes(IterCellsResolution *itR) { + // reached the end of over iterator; emits H3_NULL from now on + if (itR->h == H3_NULL) return; + + // step child iterator + iterStepChild(&(itR->_itC)); + + // If the child iterator is exhausted and there are still + // base cells remaining, we initialize the next base cell child iterator + if ((itR->_itC.h == H3_NULL) && (itR->_baseCellNum + 1 < NUM_BASE_CELLS)) { + itR->_baseCellNum += 1; + itR->_itC = iterInitBaseCellNum(itR->_baseCellNum, itR->_res); + } + + // This overall iterator reflects the next cell in the child iterator. + // Note: This sets itR->h = H3_NULL if the base cells were + // exhausted in the check above. + itR->h = itR->_itC.h; +} diff --git a/src/h3lib/lib/latLng.c b/src/h3lib/lib/latLng.c new file mode 100644 index 0000000..42fd820 --- /dev/null +++ b/src/h3lib/lib/latLng.c @@ -0,0 +1,463 @@ +/* + * Copyright 2016-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file latLng.c + * @brief Functions for working with lat/lng coordinates. + */ + +#include "latLng.h" + +#include +#include + +#include "constants.h" +#include "h3Assert.h" +#include "h3api.h" +#include "mathExtensions.h" + +/** + * Normalizes radians to a value between 0.0 and two PI. + * + * @param rads The input radians value. + * @return The normalized radians value. + */ +double _posAngleRads(double rads) { + double tmp = ((rads < 0.0L) ? rads + M_2PI : rads); + if (rads >= M_2PI) tmp -= M_2PI; + return tmp; +} + +/** + * Determines if the components of two spherical coordinates are within some + * threshold distance of each other. + * + * @param p1 The first spherical coordinates. + * @param p2 The second spherical coordinates. + * @param threshold The threshold distance. + * @return Whether or not the two coordinates are within the threshold distance + * of each other. + */ +bool geoAlmostEqualThreshold(const LatLng *p1, const LatLng *p2, + double threshold) { + return fabs(p1->lat - p2->lat) < threshold && + fabs(p1->lng - p2->lng) < threshold; +} + +/** + * Determines if the components of two spherical coordinates are within our + * standard epsilon distance of each other. + * + * @param p1 The first spherical coordinates. + * @param p2 The second spherical coordinates. + * @return Whether or not the two coordinates are within the epsilon distance + * of each other. + */ +bool geoAlmostEqual(const LatLng *p1, const LatLng *p2) { + return geoAlmostEqualThreshold(p1, p2, EPSILON_RAD); +} + +/** + * Set the components of spherical coordinates in decimal degrees. + * + * @param p The spherical coordinates. + * @param latDegs The desired latitude in decimal degrees. + * @param lngDegs The desired longitude in decimal degrees. + */ +void setGeoDegs(LatLng *p, double latDegs, double lngDegs) { + _setGeoRads(p, H3_EXPORT(degsToRads)(latDegs), + H3_EXPORT(degsToRads)(lngDegs)); +} + +/** + * Set the components of spherical coordinates in radians. + * + * @param p The spherical coordinates. + * @param latRads The desired latitude in decimal radians. + * @param lngRads The desired longitude in decimal radians. + */ +void _setGeoRads(LatLng *p, double latRads, double lngRads) { + p->lat = latRads; + p->lng = lngRads; +} + +/** + * Convert from decimal degrees to radians. + * + * @param degrees The decimal degrees. + * @return The corresponding radians. + */ +double H3_EXPORT(degsToRads)(double degrees) { return degrees * M_PI_180; } + +/** + * Convert from radians to decimal degrees. + * + * @param radians The radians. + * @return The corresponding decimal degrees. + */ +double H3_EXPORT(radsToDegs)(double radians) { return radians * M_180_PI; } + +/** + * constrainLat makes sure latitudes are in the proper bounds + * + * @param lat The original lat value + * @return The corrected lat value + */ +double constrainLat(double lat) { + while (lat > M_PI_2) { + lat = lat - M_PI; + } + return lat; +} + +/** + * constrainLng makes sure longitudes are in the proper bounds + * + * @param lng The origin lng value + * @return The corrected lng value + */ +double constrainLng(double lng) { + while (lng > M_PI) { + lng = lng - (2 * M_PI); + } + while (lng < -M_PI) { + lng = lng + (2 * M_PI); + } + return lng; +} + +/** + * The great circle distance in radians between two spherical coordinates. + * + * This function uses the Haversine formula. + * For math details, see: + * https://en.wikipedia.org/wiki/Haversine_formula + * https://www.movable-type.co.uk/scripts/latlong.html + * + * @param a the first lat/lng pair (in radians) + * @param b the second lat/lng pair (in radians) + * + * @return the great circle distance in radians between a and b + */ +double H3_EXPORT(greatCircleDistanceRads)(const LatLng *a, const LatLng *b) { + double sinLat = sin((b->lat - a->lat) / 2.0); + double sinLng = sin((b->lng - a->lng) / 2.0); + + double A = sinLat * sinLat + cos(a->lat) * cos(b->lat) * sinLng * sinLng; + + return 2 * atan2(sqrt(A), sqrt(1 - A)); +} + +/** + * The great circle distance in kilometers between two spherical coordinates. + */ +double H3_EXPORT(greatCircleDistanceKm)(const LatLng *a, const LatLng *b) { + return H3_EXPORT(greatCircleDistanceRads)(a, b) * EARTH_RADIUS_KM; +} + +/** + * The great circle distance in meters between two spherical coordinates. + */ +double H3_EXPORT(greatCircleDistanceM)(const LatLng *a, const LatLng *b) { + return H3_EXPORT(greatCircleDistanceKm)(a, b) * 1000; +} + +/** + * Determines the azimuth to p2 from p1 in radians. + * + * @param p1 The first spherical coordinates. + * @param p2 The second spherical coordinates. + * @return The azimuth in radians from p1 to p2. + */ +double _geoAzimuthRads(const LatLng *p1, const LatLng *p2) { + return atan2(cos(p2->lat) * sin(p2->lng - p1->lng), + cos(p1->lat) * sin(p2->lat) - + sin(p1->lat) * cos(p2->lat) * cos(p2->lng - p1->lng)); +} + +/** + * Computes the point on the sphere a specified azimuth and distance from + * another point. + * + * @param p1 The first spherical coordinates. + * @param az The desired azimuth from p1. + * @param distance The desired distance from p1, must be non-negative. + * @param p2 The spherical coordinates at the desired azimuth and distance from + * p1. + */ +void _geoAzDistanceRads(const LatLng *p1, double az, double distance, + LatLng *p2) { + if (distance < EPSILON) { + *p2 = *p1; + return; + } + + double sinlat, sinlng, coslng; + + az = _posAngleRads(az); + + // check for due north/south azimuth + if (az < EPSILON || fabs(az - M_PI) < EPSILON) { + if (az < EPSILON) // due north + p2->lat = p1->lat + distance; + else // due south + p2->lat = p1->lat - distance; + + if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole + { + p2->lat = M_PI_2; + p2->lng = 0.0; + } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole + { + p2->lat = -M_PI_2; + p2->lng = 0.0; + } else + p2->lng = constrainLng(p1->lng); + } else // not due north or south + { + sinlat = sin(p1->lat) * cos(distance) + + cos(p1->lat) * sin(distance) * cos(az); + if (sinlat > 1.0) sinlat = 1.0; + if (sinlat < -1.0) sinlat = -1.0; + p2->lat = asin(sinlat); + if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole + { + p2->lat = M_PI_2; + p2->lng = 0.0; + } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole + { + p2->lat = -M_PI_2; + p2->lng = 0.0; + } else { + sinlng = sin(az) * sin(distance) / cos(p2->lat); + coslng = (cos(distance) - sin(p1->lat) * sin(p2->lat)) / + cos(p1->lat) / cos(p2->lat); + if (sinlng > 1.0) sinlng = 1.0; + if (sinlng < -1.0) sinlng = -1.0; + if (coslng > 1.0) coslng = 1.0; + if (coslng < -1.0) coslng = -1.0; + p2->lng = constrainLng(p1->lng + atan2(sinlng, coslng)); + } + } +} + +/* + * The following functions provide meta information about the H3 hexagons at + * each zoom level. Since there are only 16 total levels, these are current + * handled with hardwired static values, but it may be worthwhile to put these + * static values into another file that can be autogenerated by source code in + * the future. + */ + +H3Error H3_EXPORT(getHexagonAreaAvgKm2)(int res, double *out) { + static const double areas[] = { + 4.357449416078383e+06, 6.097884417941332e+05, 8.680178039899720e+04, + 1.239343465508816e+04, 1.770347654491307e+03, 2.529038581819449e+02, + 3.612906216441245e+01, 5.161293359717191e+00, 7.373275975944177e-01, + 1.053325134272067e-01, 1.504750190766435e-02, 2.149643129451879e-03, + 3.070918756316060e-04, 4.387026794728296e-05, 6.267181135324313e-06, + 8.953115907605790e-07}; + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = areas[res]; + return E_SUCCESS; +} + +H3Error H3_EXPORT(getHexagonAreaAvgM2)(int res, double *out) { + static const double areas[] = { + 4.357449416078390e+12, 6.097884417941339e+11, 8.680178039899731e+10, + 1.239343465508818e+10, 1.770347654491309e+09, 2.529038581819452e+08, + 3.612906216441250e+07, 5.161293359717198e+06, 7.373275975944188e+05, + 1.053325134272069e+05, 1.504750190766437e+04, 2.149643129451882e+03, + 3.070918756316063e+02, 4.387026794728301e+01, 6.267181135324322e+00, + 8.953115907605802e-01}; + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = areas[res]; + return E_SUCCESS; +} + +H3Error H3_EXPORT(getHexagonEdgeLengthAvgKm)(int res, double *out) { + static const double lens[] = { + 1107.712591, 418.6760055, 158.2446558, 59.81085794, + 22.6063794, 8.544408276, 3.229482772, 1.220629759, + 0.461354684, 0.174375668, 0.065907807, 0.024910561, + 0.009415526, 0.003559893, 0.001348575, 0.000509713}; + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = lens[res]; + return E_SUCCESS; +} + +H3Error H3_EXPORT(getHexagonEdgeLengthAvgM)(int res, double *out) { + static const double lens[] = { + 1107712.591, 418676.0055, 158244.6558, 59810.85794, + 22606.3794, 8544.408276, 3229.482772, 1220.629759, + 461.3546837, 174.3756681, 65.90780749, 24.9105614, + 9.415526211, 3.559893033, 1.348574562, 0.509713273}; + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = lens[res]; + return E_SUCCESS; +} + +H3Error H3_EXPORT(getNumCells)(int res, int64_t *out) { + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = 2 + 120 * _ipow(7, res); + return E_SUCCESS; +} + +/** + * Surface area in radians^2 of spherical triangle on unit sphere. + * + * For the math, see: + * https://en.wikipedia.org/wiki/Spherical_trigonometry#Area_and_spherical_excess + * + * @param a length of triangle side A in radians + * @param b length of triangle side B in radians + * @param c length of triangle side C in radians + * + * @return area in radians^2 of triangle on unit sphere + */ +double triangleEdgeLengthsToArea(double a, double b, double c) { + double s = (a + b + c) / 2; + + a = (s - a) / 2; + b = (s - b) / 2; + c = (s - c) / 2; + s = s / 2; + + return 4 * atan(sqrt(tan(s) * tan(a) * tan(b) * tan(c))); +} + +/** + * Compute area in radians^2 of a spherical triangle, given its vertices. + * + * @param a vertex lat/lng in radians + * @param b vertex lat/lng in radians + * @param c vertex lat/lng in radians + * + * @return area of triangle on unit sphere, in radians^2 + */ +double triangleArea(const LatLng *a, const LatLng *b, const LatLng *c) { + return triangleEdgeLengthsToArea(H3_EXPORT(greatCircleDistanceRads)(a, b), + H3_EXPORT(greatCircleDistanceRads)(b, c), + H3_EXPORT(greatCircleDistanceRads)(c, a)); +} + +/** + * Area of H3 cell in radians^2. + * + * The area is calculated by breaking the cell into spherical triangles and + * summing up their areas. Note that some H3 cells (hexagons and pentagons) + * are irregular, and have more than 6 or 5 sides. + * + * todo: optimize the computation by re-using the edges shared between triangles + * + * @param cell H3 cell + * @param out cell area in radians^2 + * @return E_SUCCESS on success, or an error code otherwise + */ +H3Error H3_EXPORT(cellAreaRads2)(H3Index cell, double *out) { + LatLng c; + CellBoundary cb; + H3Error err = H3_EXPORT(cellToLatLng)(cell, &c); + if (err) { + return err; + } + err = H3_EXPORT(cellToBoundary)(cell, &cb); + if (NEVER(err)) { + // Uncoverable because cellToLatLng will have returned an error already + return err; + } + + double area = 0.0; + for (int i = 0; i < cb.numVerts; i++) { + int j = (i + 1) % cb.numVerts; + area += triangleArea(&cb.verts[i], &cb.verts[j], &c); + } + + *out = area; + return E_SUCCESS; +} + +/** + * Area of H3 cell in kilometers^2. + */ +H3Error H3_EXPORT(cellAreaKm2)(H3Index cell, double *out) { + H3Error err = H3_EXPORT(cellAreaRads2)(cell, out); + if (!err) { + *out = *out * EARTH_RADIUS_KM * EARTH_RADIUS_KM; + } + return err; +} + +/** + * Area of H3 cell in meters^2. + */ +H3Error H3_EXPORT(cellAreaM2)(H3Index cell, double *out) { + H3Error err = H3_EXPORT(cellAreaKm2)(cell, out); + if (!err) { + *out = *out * 1000 * 1000; + } + return err; +} + +/** + * Length of a directed edge in radians. + * + * @param edge H3 directed edge + * + * @return length in radians + */ +H3Error H3_EXPORT(edgeLengthRads)(H3Index edge, double *length) { + CellBoundary cb; + + H3Error err = H3_EXPORT(directedEdgeToBoundary)(edge, &cb); + if (err) { + return err; + } + + *length = 0.0; + for (int i = 0; i < cb.numVerts - 1; i++) { + *length += + H3_EXPORT(greatCircleDistanceRads)(&cb.verts[i], &cb.verts[i + 1]); + } + + return E_SUCCESS; +} + +/** + * Length of a directed edge in kilometers. + */ +H3Error H3_EXPORT(edgeLengthKm)(H3Index edge, double *length) { + H3Error err = H3_EXPORT(edgeLengthRads)(edge, length); + *length = *length * EARTH_RADIUS_KM; + return err; +} + +/** + * Length of a directed edge in meters. + */ +H3Error H3_EXPORT(edgeLengthM)(H3Index edge, double *length) { + H3Error err = H3_EXPORT(edgeLengthKm)(edge, length); + *length = *length * 1000; + return err; +} diff --git a/src/h3lib/lib/linkedGeo.c b/src/h3lib/lib/linkedGeo.c new file mode 100644 index 0000000..85308e8 --- /dev/null +++ b/src/h3lib/lib/linkedGeo.c @@ -0,0 +1,380 @@ +/* + * Copyright 2017-2018, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file linkedGeo.c + * @brief Linked data structure for geo data + */ + +#include "linkedGeo.h" + +#include +#include + +#include "alloc.h" +#include "h3api.h" +#include "latLng.h" + +/** + * Add a linked polygon to the current polygon + * @param polygon Polygon to add link to + * @return Pointer to new polygon + */ +LinkedGeoPolygon *addNewLinkedPolygon(LinkedGeoPolygon *polygon) { + assert(polygon->next == NULL); + LinkedGeoPolygon *next = H3_MEMORY(calloc)(1, sizeof(*next)); + assert(next != NULL); + polygon->next = next; + return next; +} + +/** + * Add a new linked loop to the current polygon + * @param polygon Polygon to add loop to + * @return Pointer to loop + */ +LinkedGeoLoop *addNewLinkedLoop(LinkedGeoPolygon *polygon) { + LinkedGeoLoop *loop = H3_MEMORY(calloc)(1, sizeof(*loop)); + assert(loop != NULL); + return addLinkedLoop(polygon, loop); +} + +/** + * Add an existing linked loop to the current polygon + * @param polygon Polygon to add loop to + * @return Pointer to loop + */ +LinkedGeoLoop *addLinkedLoop(LinkedGeoPolygon *polygon, LinkedGeoLoop *loop) { + LinkedGeoLoop *last = polygon->last; + if (last == NULL) { + assert(polygon->first == NULL); + polygon->first = loop; + } else { + last->next = loop; + } + polygon->last = loop; + return loop; +} + +/** + * Add a new linked coordinate to the current loop + * @param loop Loop to add coordinate to + * @param vertex Coordinate to add + * @return Pointer to the coordinate + */ +LinkedLatLng *addLinkedCoord(LinkedGeoLoop *loop, const LatLng *vertex) { + LinkedLatLng *coord = H3_MEMORY(malloc)(sizeof(*coord)); + assert(coord != NULL); + *coord = (LinkedLatLng){.vertex = *vertex, .next = NULL}; + LinkedLatLng *last = loop->last; + if (last == NULL) { + assert(loop->first == NULL); + loop->first = coord; + } else { + last->next = coord; + } + loop->last = coord; + return coord; +} + +/** + * Free all allocated memory for a linked geo loop. The caller is + * responsible for freeing memory allocated to input loop struct. + * @param loop Loop to free + */ +void destroyLinkedGeoLoop(LinkedGeoLoop *loop) { + LinkedLatLng *nextCoord; + for (LinkedLatLng *currentCoord = loop->first; currentCoord != NULL; + currentCoord = nextCoord) { + nextCoord = currentCoord->next; + H3_MEMORY(free)(currentCoord); + } +} + +/** + * Free all allocated memory for a linked geo structure. The caller is + * responsible for freeing memory allocated to input polygon struct. + * @param polygon Pointer to the first polygon in the structure + */ +void H3_EXPORT(destroyLinkedMultiPolygon)(LinkedGeoPolygon *polygon) { + // flag to skip the input polygon + bool skip = true; + LinkedGeoPolygon *nextPolygon; + LinkedGeoLoop *nextLoop; + for (LinkedGeoPolygon *currentPolygon = polygon; currentPolygon != NULL; + currentPolygon = nextPolygon) { + for (LinkedGeoLoop *currentLoop = currentPolygon->first; + currentLoop != NULL; currentLoop = nextLoop) { + destroyLinkedGeoLoop(currentLoop); + nextLoop = currentLoop->next; + H3_MEMORY(free)(currentLoop); + } + nextPolygon = currentPolygon->next; + if (skip) { + // do not free the input polygon + skip = false; + } else { + H3_MEMORY(free)(currentPolygon); + } + } +} + +/** + * Count the number of polygons in a linked list + * @param polygon Starting polygon + * @return Count + */ +int countLinkedPolygons(LinkedGeoPolygon *polygon) { + int count = 0; + while (polygon != NULL) { + count++; + polygon = polygon->next; + } + return count; +} + +/** + * Count the number of linked loops in a polygon + * @param polygon Polygon to count loops for + * @return Count + */ +int countLinkedLoops(LinkedGeoPolygon *polygon) { + LinkedGeoLoop *loop = polygon->first; + int count = 0; + while (loop != NULL) { + count++; + loop = loop->next; + } + return count; +} + +/** + * Count the number of coordinates in a loop + * @param loop Loop to count coordinates for + * @return Count + */ +int countLinkedCoords(LinkedGeoLoop *loop) { + LinkedLatLng *coord = loop->first; + int count = 0; + while (coord != NULL) { + count++; + coord = coord->next; + } + return count; +} + +/** + * Count the number of polygons containing a given loop. + * @param loop Loop to count containers for + * @param polygons Polygons to test + * @param bboxes Bounding boxes for polygons, used in point-in-poly check + * @param polygonCount Number of polygons in the test array + * @return Number of polygons containing the loop + */ +static int countContainers(const LinkedGeoLoop *loop, + const LinkedGeoPolygon **polygons, + const BBox **bboxes, const int polygonCount) { + int containerCount = 0; + for (int i = 0; i < polygonCount; i++) { + if (loop != polygons[i]->first && + pointInsideLinkedGeoLoop(polygons[i]->first, bboxes[i], + &loop->first->vertex)) { + containerCount++; + } + } + return containerCount; +} + +/** + * Given a list of nested containers, find the one most deeply nested. + * @param polygons Polygon containers to check + * @param bboxes Bounding boxes for polygons, used in point-in-poly check + * @param polygonCount Number of polygons in the list + * @return Deepest container, or null if list is empty + */ +static const LinkedGeoPolygon *findDeepestContainer( + const LinkedGeoPolygon **polygons, const BBox **bboxes, + const int polygonCount) { + // Set the initial return value to the first candidate + const LinkedGeoPolygon *parent = polygonCount > 0 ? polygons[0] : NULL; + + // If we have multiple polygons, they must be nested inside each other. + // Find the innermost polygon by taking the one with the most containers + // in the list. + if (polygonCount > 1) { + int max = -1; + for (int i = 0; i < polygonCount; i++) { + int count = countContainers(polygons[i]->first, polygons, bboxes, + polygonCount); + if (count > max) { + parent = polygons[i]; + max = count; + } + } + } + + return parent; +} + +/** + * Find the polygon to which a given hole should be allocated. Note that this + * function will return null if no parent is found. + * @param loop Inner loop describing a hole + * @param polygon Head of a linked list of polygons to check + * @param bboxes Bounding boxes for polygons, used in point-in-poly check + * @param polygonCount Number of polygons to check + * @return Pointer to parent polygon, or null if not found + */ +static const LinkedGeoPolygon *findPolygonForHole( + const LinkedGeoLoop *loop, const LinkedGeoPolygon *polygon, + const BBox *bboxes, const int polygonCount) { + // Early exit with no polygons + if (polygonCount == 0) { + return NULL; + } + // Initialize arrays for candidate loops and their bounding boxes + const LinkedGeoPolygon **candidates = + H3_MEMORY(malloc)(polygonCount * sizeof(LinkedGeoPolygon *)); + assert(candidates != NULL); + const BBox **candidateBBoxes = + H3_MEMORY(malloc)(polygonCount * sizeof(BBox *)); + assert(candidateBBoxes != NULL); + + // Find all polygons that contain the loop + int candidateCount = 0; + int index = 0; + while (polygon) { + // We are guaranteed not to overlap, so just test the first point + if (pointInsideLinkedGeoLoop(polygon->first, &bboxes[index], + &loop->first->vertex)) { + candidates[candidateCount] = polygon; + candidateBBoxes[candidateCount] = &bboxes[index]; + candidateCount++; + } + polygon = polygon->next; + index++; + } + + // The most deeply nested container is the immediate parent + const LinkedGeoPolygon *parent = + findDeepestContainer(candidates, candidateBBoxes, candidateCount); + + // Free allocated memory + H3_MEMORY(free)(candidates); + H3_MEMORY(free)(candidateBBoxes); + + return parent; +} + +/** + * Normalize a LinkedGeoPolygon in-place into a structure following GeoJSON + * MultiPolygon rules: Each polygon must have exactly one outer loop, which + * must be first in the list, followed by any holes. Holes in this algorithm + * are identified by winding order (holes are clockwise), which is guaranteed + * by the h3SetToVertexGraph algorithm. + * + * Input to this function is assumed to be a single polygon including all + * loops to normalize. It's assumed that a valid arrangement is possible. + * + * @param root Root polygon including all loops + * @return 0 on success, or an error code > 0 for invalid input + */ +H3Error normalizeMultiPolygon(LinkedGeoPolygon *root) { + // We assume that the input is a single polygon with loops; + // if it has multiple polygons, don't touch it + if (root->next) { + return E_FAILED; + } + + // Count loops, exiting early if there's only one + int loopCount = countLinkedLoops(root); + if (loopCount <= 1) { + return E_SUCCESS; + } + + H3Error resultCode = E_SUCCESS; + LinkedGeoPolygon *polygon = NULL; + LinkedGeoLoop *next; + int innerCount = 0; + int outerCount = 0; + + // Create an array to hold all of the inner loops. Note that + // this array will never be full, as there will always be fewer + // inner loops than outer loops. + LinkedGeoLoop **innerLoops = + H3_MEMORY(malloc)(loopCount * sizeof(LinkedGeoLoop *)); + assert(innerLoops != NULL); + + // Create an array to hold the bounding boxes for the outer loops + BBox *bboxes = H3_MEMORY(malloc)(loopCount * sizeof(BBox)); + assert(bboxes != NULL); + + // Get the first loop and unlink it from root + LinkedGeoLoop *loop = root->first; + *root = (LinkedGeoPolygon){0}; + + // Iterate over all loops, moving inner loops into an array and + // assigning outer loops to new polygons + while (loop) { + if (isClockwiseLinkedGeoLoop(loop)) { + innerLoops[innerCount] = loop; + innerCount++; + } else { + polygon = polygon == NULL ? root : addNewLinkedPolygon(polygon); + addLinkedLoop(polygon, loop); + bboxFromLinkedGeoLoop(loop, &bboxes[outerCount]); + outerCount++; + } + // get the next loop and unlink it from this one + next = loop->next; + loop->next = NULL; + loop = next; + } + + // Find polygon for each inner loop and assign the hole to it + for (int i = 0; i < innerCount; i++) { + polygon = (LinkedGeoPolygon *)findPolygonForHole(innerLoops[i], root, + bboxes, outerCount); + if (polygon) { + addLinkedLoop(polygon, innerLoops[i]); + } else { + // If we can't find a polygon (possible with invalid input), then + // we need to release the memory for the hole, because the loop has + // been unlinked from the root and the caller will no longer have + // a way to destroy it with destroyLinkedMultiPolygon. + destroyLinkedGeoLoop(innerLoops[i]); + H3_MEMORY(free)(innerLoops[i]); + resultCode = E_FAILED; + } + } + + // Free allocated memory + H3_MEMORY(free)(innerLoops); + H3_MEMORY(free)(bboxes); + + return resultCode; +} + +// Define macros used in polygon algos for LinkedGeoLoop +#define TYPE LinkedGeoLoop +#define INIT_ITERATION INIT_ITERATION_LINKED_LOOP +#define ITERATE ITERATE_LINKED_LOOP +#define IS_EMPTY IS_EMPTY_LINKED_LOOP + +#include "polygonAlgos.h" + +#undef TYPE +#undef IS_EMPTY +#undef INIT_ITERATION +#undef ITERATE diff --git a/src/h3lib/lib/localij.c b/src/h3lib/lib/localij.c new file mode 100644 index 0000000..c8df07a --- /dev/null +++ b/src/h3lib/lib/localij.c @@ -0,0 +1,728 @@ +/* + * Copyright 2018-2020 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file localij.c + * @brief Local IJ coordinate space functions + * + * These functions try to provide a useful coordinate space in the vicinity of + * an origin index. + */ +#include +#include +#include +#include +#include + +#include "baseCells.h" +#include "faceijk.h" +#include "h3Assert.h" +#include "h3Index.h" +#include "mathExtensions.h" + +/** + * Origin leading digit -> index leading digit -> rotations 60 cw + * Either being 1 (K axis) is invalid. + * No good default at 0. + */ +const int PENTAGON_ROTATIONS[7][7] = { + {0, -1, 0, 0, 0, 0, 0}, // 0 + {-1, -1, -1, -1, -1, -1, -1}, // 1 + {0, -1, 0, 0, 0, 1, 0}, // 2 + {0, -1, 0, 0, 1, 1, 0}, // 3 + {0, -1, 0, 5, 0, 0, 0}, // 4 + {0, -1, 5, 5, 0, 0, 0}, // 5 + {0, -1, 0, 0, 0, 0, 0}, // 6 +}; +/** + * Reverse base cell direction -> leading index digit -> rotations 60 ccw. + * For reversing the rotation introduced in PENTAGON_ROTATIONS when + * the origin is on a pentagon (regardless of the base cell of the index.) + */ +const int PENTAGON_ROTATIONS_REVERSE[7][7] = { + {0, 0, 0, 0, 0, 0, 0}, // 0 + {-1, -1, -1, -1, -1, -1, -1}, // 1 + {0, 1, 0, 0, 0, 0, 0}, // 2 + {0, 1, 0, 0, 0, 1, 0}, // 3 + {0, 5, 0, 0, 0, 0, 0}, // 4 + {0, 5, 0, 5, 0, 0, 0}, // 5 + {0, 0, 0, 0, 0, 0, 0}, // 6 +}; +/** + * Reverse base cell direction -> leading index digit -> rotations 60 ccw. + * For reversing the rotation introduced in PENTAGON_ROTATIONS when the index is + * on a pentagon and the origin is not. + */ +const int PENTAGON_ROTATIONS_REVERSE_NONPOLAR[7][7] = { + {0, 0, 0, 0, 0, 0, 0}, // 0 + {-1, -1, -1, -1, -1, -1, -1}, // 1 + {0, 1, 0, 0, 0, 0, 0}, // 2 + {0, 1, 0, 0, 0, 1, 0}, // 3 + {0, 5, 0, 0, 0, 0, 0}, // 4 + {0, 1, 0, 5, 1, 1, 0}, // 5 + {0, 0, 0, 0, 0, 0, 0}, // 6 +}; +/** + * Reverse base cell direction -> leading index digit -> rotations 60 ccw. + * For reversing the rotation introduced in PENTAGON_ROTATIONS when the index is + * on a polar pentagon and the origin is not. + */ +const int PENTAGON_ROTATIONS_REVERSE_POLAR[7][7] = { + {0, 0, 0, 0, 0, 0, 0}, // 0 + {-1, -1, -1, -1, -1, -1, -1}, // 1 + {0, 1, 1, 1, 1, 1, 1}, // 2 + {0, 1, 0, 0, 0, 1, 0}, // 3 + {0, 1, 0, 0, 1, 1, 1}, // 4 + {0, 1, 0, 5, 1, 1, 0}, // 5 + {0, 1, 1, 0, 1, 1, 1}, // 6 +}; + +/** + * Prohibited directions when unfolding a pentagon. + * + * Indexes by two directions, both relative to the pentagon base cell. The first + * is the direction of the origin index and the second is the direction of the + * index to unfold. Direction refers to the direction from base cell to base + * cell if the indexes are on different base cells, or the leading digit if + * within the pentagon base cell. + * + * This previously included a Class II/Class III check but these were removed + * due to failure cases. It's possible this could be restricted to a narrower + * set of a failure cases. Currently, the logic is any unfolding across more + * than one icosahedron face is not permitted. + */ +const bool FAILED_DIRECTIONS[7][7] = { + {false, false, false, false, false, false, false}, // 0 + {false, false, false, false, false, false, false}, // 1 + {false, false, false, false, true, true, false}, // 2 + {false, false, false, false, true, false, true}, // 3 + {false, false, true, true, false, false, false}, // 4 + {false, false, true, false, false, false, true}, // 5 + {false, false, false, true, false, true, false}, // 6 +}; + +/** + * Produces ijk+ coordinates for an index anchored by an origin. + * + * The coordinate space used by this function may have deleted + * regions or warping due to pentagonal distortion. + * + * Coordinates are only comparable if they come from the same + * origin index. + * + * Failure may occur if the index is too far away from the origin + * or if the index is on the other side of a pentagon. + * + * @param origin An anchoring index for the ijk+ coordinate system. + * @param index Index to find the coordinates of + * @param out ijk+ coordinates of the index will be placed here on success + * @return 0 on success, or another value on failure. + */ +H3Error cellToLocalIjk(H3Index origin, H3Index h3, CoordIJK *out) { + int res = H3_GET_RESOLUTION(origin); + + if (res != H3_GET_RESOLUTION(h3)) { + return E_RES_MISMATCH; + } + + int originBaseCell = H3_GET_BASE_CELL(origin); + int baseCell = H3_GET_BASE_CELL(h3); + + if (NEVER(originBaseCell < 0) || originBaseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } + if (NEVER(baseCell < 0) || baseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } + + // Direction from origin base cell to index base cell + Direction dir = CENTER_DIGIT; + Direction revDir = CENTER_DIGIT; + if (originBaseCell != baseCell) { + dir = _getBaseCellDirection(originBaseCell, baseCell); + if (dir == INVALID_DIGIT) { + // Base cells are not neighbors, can't unfold. + return E_FAILED; + } + revDir = _getBaseCellDirection(baseCell, originBaseCell); + assert(revDir != INVALID_DIGIT); + } + + int originOnPent = _isBaseCellPentagon(originBaseCell); + int indexOnPent = _isBaseCellPentagon(baseCell); + + FaceIJK indexFijk = {0}; + if (dir != CENTER_DIGIT) { + // Rotate index into the orientation of the origin base cell. + // cw because we are undoing the rotation into that base cell. + int baseCellRotations = baseCellNeighbor60CCWRots[originBaseCell][dir]; + if (indexOnPent) { + for (int i = 0; i < baseCellRotations; i++) { + h3 = _h3RotatePent60cw(h3); + + revDir = _rotate60cw(revDir); + if (revDir == K_AXES_DIGIT) revDir = _rotate60cw(revDir); + } + } else { + for (int i = 0; i < baseCellRotations; i++) { + h3 = _h3Rotate60cw(h3); + + revDir = _rotate60cw(revDir); + } + } + } + // Face is unused. This produces coordinates in base cell coordinate space. + _h3ToFaceIjkWithInitializedFijk(h3, &indexFijk); + + if (dir != CENTER_DIGIT) { + assert(baseCell != originBaseCell); + assert(!(originOnPent && indexOnPent)); + + int pentagonRotations = 0; + int directionRotations = 0; + + if (originOnPent) { + int originLeadingDigit = _h3LeadingNonZeroDigit(origin); + + if (originLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + if (FAILED_DIRECTIONS[originLeadingDigit][dir]) { + // TODO: We may be unfolding the pentagon incorrectly in this + // case; return an error code until this is guaranteed to be + // correct. + return E_FAILED; + } + + directionRotations = PENTAGON_ROTATIONS[originLeadingDigit][dir]; + pentagonRotations = directionRotations; + } else if (indexOnPent) { + int indexLeadingDigit = _h3LeadingNonZeroDigit(h3); + + if (indexLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + if (FAILED_DIRECTIONS[indexLeadingDigit][revDir]) { + // TODO: We may be unfolding the pentagon incorrectly in this + // case; return an error code until this is guaranteed to be + // correct. + return E_FAILED; + } + + pentagonRotations = PENTAGON_ROTATIONS[revDir][indexLeadingDigit]; + } + + if (pentagonRotations < 0 || directionRotations < 0) { + // This occurs when an invalid K axis digit is present + return E_CELL_INVALID; + } + + for (int i = 0; i < pentagonRotations; i++) { + _ijkRotate60cw(&indexFijk.coord); + } + + CoordIJK offset = {0}; + _neighbor(&offset, dir); + // Scale offset based on resolution + for (int r = res - 1; r >= 0; r--) { + if (isResolutionClassIII(r + 1)) { + // rotate ccw + _downAp7(&offset); + } else { + // rotate cw + _downAp7r(&offset); + } + } + + for (int i = 0; i < directionRotations; i++) { + _ijkRotate60cw(&offset); + } + + // Perform necessary translation + _ijkAdd(&indexFijk.coord, &offset, &indexFijk.coord); + _ijkNormalize(&indexFijk.coord); + } else if (originOnPent && indexOnPent) { + // If the origin and index are on pentagon, and we checked that the base + // cells are the same or neighboring, then they must be the same base + // cell. + assert(baseCell == originBaseCell); + + int originLeadingDigit = _h3LeadingNonZeroDigit(origin); + int indexLeadingDigit = _h3LeadingNonZeroDigit(h3); + + if (originLeadingDigit == INVALID_DIGIT || + indexLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + if (FAILED_DIRECTIONS[originLeadingDigit][indexLeadingDigit]) { + // TODO: We may be unfolding the pentagon incorrectly in this case; + // return an error code until this is guaranteed to be correct. + return E_FAILED; + } + + int withinPentagonRotations = + PENTAGON_ROTATIONS[originLeadingDigit][indexLeadingDigit]; + + for (int i = 0; i < withinPentagonRotations; i++) { + _ijkRotate60cw(&indexFijk.coord); + } + } + + *out = indexFijk.coord; + return E_SUCCESS; +} + +/** + * Produces an index for ijk+ coordinates anchored by an origin. + * + * The coordinate space used by this function may have deleted + * regions or warping due to pentagonal distortion. + * + * Failure may occur if the coordinates are too far away from the origin + * or if the index is on the other side of a pentagon. + * + * @param origin An anchoring index for the ijk+ coordinate system. + * @param ijk IJK+ Coordinates to find the index of + * @param out The index will be placed here on success + * @return 0 on success, or another value on failure. + */ +H3Error localIjkToCell(H3Index origin, const CoordIJK *ijk, H3Index *out) { + int res = H3_GET_RESOLUTION(origin); + int originBaseCell = H3_GET_BASE_CELL(origin); + if (NEVER(originBaseCell < 0) || originBaseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } + int originOnPent = _isBaseCellPentagon(originBaseCell); + + // This logic is very similar to faceIjkToH3 + // initialize the index + *out = H3_INIT; + H3_SET_MODE(*out, H3_CELL_MODE); + H3_SET_RESOLUTION(*out, res); + + // check for res 0/base cell + if (res == 0) { + const Direction dir = _unitIjkToDigit(ijk); + if (dir == INVALID_DIGIT) { + // out of range input - not a unit vector or zero vector + return E_FAILED; + } + + const int newBaseCell = _getBaseCellNeighbor(originBaseCell, dir); + if (newBaseCell == INVALID_BASE_CELL) { + // Moving in an invalid direction off a pentagon. + return E_FAILED; + } + H3_SET_BASE_CELL(*out, newBaseCell); + return E_SUCCESS; + } + + // we need to find the correct base cell offset (if any) for this H3 index; + // start with the passed in base cell and resolution res ijk coordinates + // in that base cell's coordinate system + CoordIJK ijkCopy = *ijk; + + // build the H3Index from finest res up + // adjust r for the fact that the res 0 base cell offsets the indexing + // digits + for (int r = res - 1; r >= 0; r--) { + CoordIJK lastIJK = ijkCopy; + CoordIJK lastCenter; + if (isResolutionClassIII(r + 1)) { + // rotate ccw + H3Error upAp7Error = _upAp7Checked(&ijkCopy); + if (upAp7Error) { + return upAp7Error; + } + lastCenter = ijkCopy; + _downAp7(&lastCenter); + } else { + // rotate cw + H3Error upAp7rError = _upAp7rChecked(&ijkCopy); + if (upAp7rError) { + return upAp7rError; + } + lastCenter = ijkCopy; + _downAp7r(&lastCenter); + } + + CoordIJK diff; + _ijkSub(&lastIJK, &lastCenter, &diff); + _ijkNormalize(&diff); + + H3_SET_INDEX_DIGIT(*out, r + 1, _unitIjkToDigit(&diff)); + } + + // ijkCopy should now hold the IJK of the base cell in the + // coordinate system of the current base cell + + if (ijkCopy.i > 1 || ijkCopy.j > 1 || ijkCopy.k > 1) { + // out of range input + return E_FAILED; + } + + // lookup the correct base cell + Direction dir = _unitIjkToDigit(&ijkCopy); + int baseCell = _getBaseCellNeighbor(originBaseCell, dir); + // If baseCell is invalid, it must be because the origin base cell is a + // pentagon, and because pentagon base cells do not border each other, + // baseCell must not be a pentagon. + int indexOnPent = + (baseCell == INVALID_BASE_CELL ? 0 : _isBaseCellPentagon(baseCell)); + + if (dir != CENTER_DIGIT) { + // If the index is in a warped direction, we need to unwarp the base + // cell direction. There may be further need to rotate the index digits. + int pentagonRotations = 0; + if (originOnPent) { + const Direction originLeadingDigit = _h3LeadingNonZeroDigit(origin); + if (originLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + pentagonRotations = + PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][dir]; + for (int i = 0; i < pentagonRotations; i++) { + dir = _rotate60ccw(dir); + } + // The pentagon rotations are being chosen so that dir is not the + // deleted direction. If it still happens, it means we're moving + // into a deleted subsequence, so there is no index here. + if (dir == K_AXES_DIGIT) { + return E_PENTAGON; + } + baseCell = _getBaseCellNeighbor(originBaseCell, dir); + + // indexOnPent does not need to be checked again since no pentagon + // base cells border each other. + assert(baseCell != INVALID_BASE_CELL); + assert(!_isBaseCellPentagon(baseCell)); + } + + // Now we can determine the relation between the origin and target base + // cell. + const int baseCellRotations = + baseCellNeighbor60CCWRots[originBaseCell][dir]; + assert(baseCellRotations >= 0); + + // Adjust for pentagon warping within the base cell. The base cell + // should be in the right location, so now we need to rotate the index + // back. We might not need to check for errors since we would just be + // double mapping. + if (indexOnPent) { + const Direction revDir = + _getBaseCellDirection(baseCell, originBaseCell); + assert(revDir != INVALID_DIGIT); + + // Adjust for the different coordinate space in the two base cells. + // This is done first because we need to do the pentagon rotations + // based on the leading digit in the pentagon's coordinate system. + for (int i = 0; i < baseCellRotations; i++) { + *out = _h3Rotate60ccw(*out); + } + + const Direction indexLeadingDigit = _h3LeadingNonZeroDigit(*out); + // This case should be unreachable because this function is building + // *out, and should never generate an invalid digit, above. + if (NEVER(indexLeadingDigit == INVALID_DIGIT)) { + return E_CELL_INVALID; + } + if (_isBaseCellPolarPentagon(baseCell)) { + pentagonRotations = + PENTAGON_ROTATIONS_REVERSE_POLAR[revDir][indexLeadingDigit]; + } else { + pentagonRotations = + PENTAGON_ROTATIONS_REVERSE_NONPOLAR[revDir] + [indexLeadingDigit]; + } + // For this to occur, revDir would need to be 1. Since revDir is + // from the index base cell (which is a pentagon) towards the + // origin, this should never be the case. + if (NEVER(pentagonRotations < 0)) { + return E_CELL_INVALID; + } + + for (int i = 0; i < pentagonRotations; i++) { + *out = _h3RotatePent60ccw(*out); + } + } else { + if (pentagonRotations < 0) { + return E_CELL_INVALID; + } + for (int i = 0; i < pentagonRotations; i++) { + *out = _h3Rotate60ccw(*out); + } + + // Adjust for the different coordinate space in the two base cells. + for (int i = 0; i < baseCellRotations; i++) { + *out = _h3Rotate60ccw(*out); + } + } + } else if (originOnPent && indexOnPent) { + const int originLeadingDigit = _h3LeadingNonZeroDigit(origin); + const int indexLeadingDigit = _h3LeadingNonZeroDigit(*out); + + if (originLeadingDigit == INVALID_DIGIT || + indexLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + const int withinPentagonRotations = + PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][indexLeadingDigit]; + if (withinPentagonRotations < 0) { + // This occurs when an invalid K axis digit is present + return E_CELL_INVALID; + } + + for (int i = 0; i < withinPentagonRotations; i++) { + *out = _h3Rotate60ccw(*out); + } + } + + if (indexOnPent) { + // TODO: There are cases in cellToLocalIjk which are failed but not + // accounted for here - instead just fail if the recovered index is + // invalid. + if (_h3LeadingNonZeroDigit(*out) == K_AXES_DIGIT) { + return E_PENTAGON; + } + } + + H3_SET_BASE_CELL(*out, baseCell); + return E_SUCCESS; +} + +/** + * Produces ij coordinates for an index anchored by an origin. + * + * The coordinate space used by this function may have deleted + * regions or warping due to pentagonal distortion. + * + * Coordinates are only comparable if they come from the same + * origin index. + * + * Failure may occur if the index is too far away from the origin + * or if the index is on the other side of a pentagon. + * + * This function's output is not guaranteed + * to be compatible across different versions of H3. + * + * @param origin An anchoring index for the ij coordinate system. + * @param index Index to find the coordinates of + * @param mode Mode, must be 0 + * @param out ij coordinates of the index will be placed here on success + * @return 0 on success, or another value on failure. + */ +H3Error H3_EXPORT(cellToLocalIj)(H3Index origin, H3Index h3, uint32_t mode, + CoordIJ *out) { + if (mode != 0) { + return E_OPTION_INVALID; + } + CoordIJK ijk; + H3Error failed = cellToLocalIjk(origin, h3, &ijk); + if (failed) { + return failed; + } + + ijkToIj(&ijk, out); + + return E_SUCCESS; +} + +/** + * Produces an index for ij coordinates anchored by an origin. + * + * The coordinate space used by this function may have deleted + * regions or warping due to pentagonal distortion. + * + * Failure may occur if the index is too far away from the origin + * or if the index is on the other side of a pentagon. + * + * This function's output is not guaranteed + * to be compatible across different versions of H3. + * + * @param origin An anchoring index for the ij coordinate system. + * @param out ij coordinates to index. + * @param mode Mode, must be 0 + * @param index Index will be placed here on success. + * @return 0 on success, or another value on failure. + */ +H3Error H3_EXPORT(localIjToCell)(H3Index origin, const CoordIJ *ij, + uint32_t mode, H3Index *out) { + if (mode != 0) { + return E_OPTION_INVALID; + } + CoordIJK ijk; + H3Error ijToIjkError = ijToIjk(ij, &ijk); + if (ijToIjkError) { + return ijToIjkError; + } + + return localIjkToCell(origin, &ijk, out); +} + +/** + * Produces the grid distance between the two indexes. + * + * This function may fail to find the distance between two indexes, for + * example if they are very far apart. It may also fail when finding + * distances for indexes on opposite sides of a pentagon. + * + * @param origin Index to find the distance from. + * @param index Index to find the distance to. + * @return The distance, or a negative number if the library could not + * compute the distance. + */ +H3Error H3_EXPORT(gridDistance)(H3Index origin, H3Index h3, int64_t *out) { + CoordIJK originIjk, h3Ijk; + H3Error originError = cellToLocalIjk(origin, origin, &originIjk); + if (originError) { + return originError; + } + H3Error destError = cellToLocalIjk(origin, h3, &h3Ijk); + if (destError) { + return destError; + } + + *out = ijkDistance(&originIjk, &h3Ijk); + return E_SUCCESS; +} + +/** + * Number of indexes in a line from the start index to the end index, + * to be used for allocating memory. Returns a negative number if the + * line cannot be computed. + * + * @param start Start index of the line + * @param end End index of the line + * @param size Size of the line + * @returns 0 on success, or another value on error + */ +H3Error H3_EXPORT(gridPathCellsSize)(H3Index start, H3Index end, + int64_t *size) { + int64_t distance; + H3Error distanceError = H3_EXPORT(gridDistance)(start, end, &distance); + if (distanceError) { + return distanceError; + } + *size = distance + 1; + return E_SUCCESS; +} + +/** + * Given cube coords as doubles, round to valid integer coordinates. Algorithm + * from https://www.redblobgames.com/grids/hexagons/#rounding + * @param i Floating-point I coord + * @param j Floating-point J coord + * @param k Floating-point K coord + * @param ijk IJK coord struct, modified in place + */ +static void cubeRound(double i, double j, double k, CoordIJK *ijk) { + int ri = round(i); + int rj = round(j); + int rk = round(k); + + double iDiff = fabs((double)ri - i); + double jDiff = fabs((double)rj - j); + double kDiff = fabs((double)rk - k); + + // Round, maintaining valid cube coords + if (iDiff > jDiff && iDiff > kDiff) { + ri = -rj - rk; + } else if (jDiff > kDiff) { + rj = -ri - rk; + } else { + rk = -ri - rj; + } + + ijk->i = ri; + ijk->j = rj; + ijk->k = rk; +} + +/** + * Given two H3 indexes, return the line of indexes between them (inclusive). + * + * This function may fail to find the line between two indexes, for + * example if they are very far apart. It may also fail when finding + * distances for indexes on opposite sides of a pentagon. + * + * Notes: + * + * - The specific output of this function should not be considered stable + * across library versions. The only guarantees the library provides are + * that the line length will be `gridDistance(start, end) + 1` and that + * every index in the line will be a neighbor of the preceding index. + * - Lines are drawn in grid space, and may not correspond exactly to either + * Cartesian lines or great arcs. + * + * @param start Start index of the line + * @param end End index of the line + * @param out Output array, which must be of size gridPathCellsSize(start, end) + * @return 0 on success, or another value on failure. + */ +H3Error H3_EXPORT(gridPathCells)(H3Index start, H3Index end, H3Index *out) { + int64_t distance; + H3Error distanceError = H3_EXPORT(gridDistance)(start, end, &distance); + // Early exit if we can't calculate the line + if (distanceError) { + return distanceError; + } + + // Get IJK coords for the start and end. We've already confirmed + // that these can be calculated with the distance check above. + CoordIJK startIjk = {0}; + CoordIJK endIjk = {0}; + + // Convert H3 addresses to IJK coords + H3Error startError = cellToLocalIjk(start, start, &startIjk); + if (NEVER(startError)) { + // Unreachable because this was called as part of gridDistance + return startError; + } + H3Error endError = cellToLocalIjk(start, end, &endIjk); + if (NEVER(endError)) { + // Unreachable because this was called as part of gridDistance + return endError; + } + + // Convert IJK to cube coordinates suitable for linear interpolation + ijkToCube(&startIjk); + ijkToCube(&endIjk); + + double iStep = + distance ? (double)(endIjk.i - startIjk.i) / (double)distance : 0; + double jStep = + distance ? (double)(endIjk.j - startIjk.j) / (double)distance : 0; + double kStep = + distance ? (double)(endIjk.k - startIjk.k) / (double)distance : 0; + + CoordIJK currentIjk = {startIjk.i, startIjk.j, startIjk.k}; + for (int64_t n = 0; n <= distance; n++) { + cubeRound((double)startIjk.i + iStep * n, + (double)startIjk.j + jStep * n, + (double)startIjk.k + kStep * n, ¤tIjk); + // Convert cube -> ijk -> h3 index + cubeToIjk(¤tIjk); + H3Error currentError = localIjkToCell(start, ¤tIjk, &out[n]); + if (currentError) { + // The cells between `start` and `end` may fall in pentagon + // distortion. + return currentError; + } + } + + return E_SUCCESS; +} diff --git a/src/h3lib/lib/mathExtensions.c b/src/h3lib/lib/mathExtensions.c new file mode 100644 index 0000000..4a69070 --- /dev/null +++ b/src/h3lib/lib/mathExtensions.c @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file mathExtensions.c + * @brief Math functions that should've been in math.h but aren't + */ + +#include "mathExtensions.h" + +/** + * _ipow does integer exponentiation efficiently. Taken from StackOverflow. + * + * @param base the integer base (can be positive or negative) + * @param exp the integer exponent (should be nonnegative) + * + * @return the exponentiated value + */ +int64_t _ipow(int64_t base, int64_t exp) { + int64_t result = 1; + while (exp) { + if (exp & 1) result *= base; + exp >>= 1; + base *= base; + } + + return result; +} diff --git a/src/h3lib/lib/polygon.c b/src/h3lib/lib/polygon.c new file mode 100644 index 0000000..ac880e7 --- /dev/null +++ b/src/h3lib/lib/polygon.c @@ -0,0 +1,85 @@ +/* + * Copyright 2018-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file polygon.c + * @brief Polygon (GeoLoop) algorithms + */ + +#include "polygon.h" + +#include +#include +#include + +#include "bbox.h" +#include "constants.h" +#include "h3api.h" +#include "latLng.h" +#include "linkedGeo.h" + +// Define macros used in polygon algos for GeoLoop +#define TYPE GeoLoop +#define INIT_ITERATION INIT_ITERATION_GEOFENCE +#define ITERATE ITERATE_GEOFENCE +#define IS_EMPTY IS_EMPTY_GEOFENCE + +#include "polygonAlgos.h" + +#undef TYPE +#undef INIT_ITERATION +#undef ITERATE +#undef IS_EMPTY + +/** + * Create a bounding box from a GeoPolygon + * @param polygon Input GeoPolygon + * @param bboxes Output bboxes, one for the outer loop and one for each hole + */ +void bboxesFromGeoPolygon(const GeoPolygon *polygon, BBox *bboxes) { + bboxFromGeoLoop(&polygon->geoloop, &bboxes[0]); + for (int i = 0; i < polygon->numHoles; i++) { + bboxFromGeoLoop(&polygon->holes[i], &bboxes[i + 1]); + } +} + +/** + * pointInsidePolygon takes a given GeoPolygon data structure and + * checks if it contains a given geo coordinate. + * + * @param geoPolygon The geoloop and holes defining the relevant area + * @param bboxes The bboxes for the main geoloop and each of its holes + * @param coord The coordinate to check + * @return Whether the point is contained + */ +bool pointInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, + const LatLng *coord) { + // Start with contains state of primary geoloop + bool contains = + pointInsideGeoLoop(&(geoPolygon->geoloop), &bboxes[0], coord); + + // If the point is contained in the primary geoloop, but there are holes in + // the geoloop iterate through all holes and return false if the point is + // contained in any hole + if (contains && geoPolygon->numHoles > 0) { + for (int i = 0; i < geoPolygon->numHoles; i++) { + if (pointInsideGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1], + coord)) { + return false; + } + } + } + + return contains; +} diff --git a/src/h3lib/lib/vec2d.c b/src/h3lib/lib/vec2d.c new file mode 100644 index 0000000..2b4a121 --- /dev/null +++ b/src/h3lib/lib/vec2d.c @@ -0,0 +1,67 @@ +/* + * Copyright 2016-2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file vec2d.c + * @brief 2D floating point vector functions. + */ + +#include "vec2d.h" + +#include +#include +#include + +/** + * Calculates the magnitude of a 2D cartesian vector. + * @param v The 2D cartesian vector. + * @return The magnitude of the vector. + */ +double _v2dMag(const Vec2d *v) { return sqrt(v->x * v->x + v->y * v->y); } + +/** + * Finds the intersection between two lines. Assumes that the lines intersect + * and that the intersection is not at an endpoint of either line. + * @param p0 The first endpoint of the first line. + * @param p1 The second endpoint of the first line. + * @param p2 The first endpoint of the second line. + * @param p3 The second endpoint of the second line. + * @param inter The intersection point. + */ +void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, + const Vec2d *p3, Vec2d *inter) { + Vec2d s1, s2; + s1.x = p1->x - p0->x; + s1.y = p1->y - p0->y; + s2.x = p3->x - p2->x; + s2.y = p3->y - p2->y; + + double t; + t = (s2.x * (p0->y - p2->y) - s2.y * (p0->x - p2->x)) / + (-s2.x * s1.y + s1.x * s2.y); + + inter->x = p0->x + (t * s1.x); + inter->y = p0->y + (t * s1.y); +} + +/** + * Whether two 2D vectors are almost equal, within some threshold + * @param v1 First vector to compare + * @param v2 Second vector to compare + * @return Whether the vectors are almost equal + */ +bool _v2dAlmostEquals(const Vec2d *v1, const Vec2d *v2) { + return fabs(v1->x - v2->x) < FLT_EPSILON && + fabs(v1->y - v2->y) < FLT_EPSILON; +} diff --git a/src/h3lib/lib/vec3d.c b/src/h3lib/lib/vec3d.c new file mode 100644 index 0000000..0b95f13 --- /dev/null +++ b/src/h3lib/lib/vec3d.c @@ -0,0 +1,56 @@ +/* + * Copyright 2018, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file vec3d.c + * @brief 3D floating point vector functions. + */ + +#include "vec3d.h" + +#include + +/** + * Square of a number + * + * @param x The input number. + * @return The square of the input number. + */ +double _square(double x) { return x * x; } + +/** + * Calculate the square of the distance between two 3D coordinates. + * + * @param v1 The first 3D coordinate. + * @param v2 The second 3D coordinate. + * @return The square of the distance between the given points. + */ +double _pointSquareDist(const Vec3d *v1, const Vec3d *v2) { + return _square(v1->x - v2->x) + _square(v1->y - v2->y) + + _square(v1->z - v2->z); +} + +/** + * Calculate the 3D coordinate on unit sphere from the latitude and longitude. + * + * @param geo The latitude and longitude of the point. + * @param v The 3D coordinate of the point. + */ +void _geoToVec3d(const LatLng *geo, Vec3d *v) { + double r = cos(geo->lat); + + v->z = sin(geo->lat); + v->x = cos(geo->lng) * r; + v->y = sin(geo->lng) * r; +} diff --git a/src/h3lib/lib/vertex.c b/src/h3lib/lib/vertex.c new file mode 100644 index 0000000..eab2940 --- /dev/null +++ b/src/h3lib/lib/vertex.c @@ -0,0 +1,373 @@ +/* + * Copyright 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file vertex.h + * @brief Functions for working with cell vertexes. + */ + +#include "vertex.h" + +#include +#include + +#include "algos.h" +#include "baseCells.h" +#include "faceijk.h" +#include "h3Assert.h" +#include "h3Index.h" +#include "latLng.h" + +#define DIRECTION_INDEX_OFFSET 2 + +/** @brief Table of direction-to-face mapping for each pentagon + * + * Note that faces are in directional order, starting at J_AXES_DIGIT. + * This table is generated by the generatePentagonDirectionFaces script. + */ +static const PentagonDirectionFaces pentagonDirectionFaces[NUM_PENTAGONS] = { + {4, {4, 0, 2, 1, 3}}, {14, {6, 11, 2, 7, 1}}, + {24, {5, 10, 1, 6, 0}}, {38, {7, 12, 3, 8, 2}}, + {49, {9, 14, 0, 5, 4}}, {58, {8, 13, 4, 9, 3}}, + {63, {11, 6, 15, 10, 16}}, {72, {12, 7, 16, 11, 17}}, + {83, {10, 5, 19, 14, 15}}, {97, {13, 8, 17, 12, 18}}, + {107, {14, 9, 18, 13, 19}}, {117, {15, 19, 17, 18, 16}}, +}; + +/** + * Get the number of CCW rotations of the cell's vertex numbers + * compared to the directional layout of its neighbors. + * @param out Number of CCW rotations for the cell + */ +static H3Error vertexRotations(H3Index cell, int *out) { + // Get the face and other info for the origin + FaceIJK fijk; + H3Error err = _h3ToFaceIjk(cell, &fijk); + if (err) { + return err; + } + int baseCell = H3_EXPORT(getBaseCellNumber)(cell); + int cellLeadingDigit = _h3LeadingNonZeroDigit(cell); + + // get the base cell face + FaceIJK baseFijk; + _baseCellToFaceIjk(baseCell, &baseFijk); + + int ccwRot60 = _baseCellToCCWrot60(baseCell, fijk.face); + + if (_isBaseCellPentagon(baseCell)) { + // Find the appropriate direction-to-face mapping + PentagonDirectionFaces dirFaces; + // We never hit the end condition + int p = 0; + for (; p < NUM_PENTAGONS; p++) { + if (pentagonDirectionFaces[p].baseCell == baseCell) { + dirFaces = pentagonDirectionFaces[p]; + break; + } + } + if (p == NUM_PENTAGONS) { + return E_FAILED; + } + + // additional CCW rotation for polar neighbors or IK neighbors + if (fijk.face != baseFijk.face && + (_isBaseCellPolarPentagon(baseCell) || + fijk.face == + dirFaces.faces[IK_AXES_DIGIT - DIRECTION_INDEX_OFFSET])) { + ccwRot60 = (ccwRot60 + 1) % 6; + } + + // Check whether the cell crosses a deleted pentagon subsequence + if (cellLeadingDigit == JK_AXES_DIGIT && + fijk.face == + dirFaces.faces[IK_AXES_DIGIT - DIRECTION_INDEX_OFFSET]) { + // Crosses from JK to IK: Rotate CW + ccwRot60 = (ccwRot60 + 5) % 6; + } else if (cellLeadingDigit == IK_AXES_DIGIT && + fijk.face == + dirFaces.faces[JK_AXES_DIGIT - DIRECTION_INDEX_OFFSET]) { + // Crosses from IK to JK: Rotate CCW + ccwRot60 = (ccwRot60 + 1) % 6; + } + } + *out = ccwRot60; + return E_SUCCESS; +} + +/** @brief Hexagon direction to vertex number relationships (same face). + * Note that we don't use direction 0 (center). + */ +static const int directionToVertexNumHex[NUM_DIGITS] = { + INVALID_DIGIT, 3, 1, 2, 5, 4, 0}; + +/** @brief Pentagon direction to vertex number relationships (same face). + * Note that we don't use directions 0 (center) or 1 (deleted K axis). + */ +static const int directionToVertexNumPent[NUM_DIGITS] = { + INVALID_DIGIT, INVALID_DIGIT, 1, 2, 4, 3, 0}; + +/** + * Get the first vertex number for a given direction. The neighbor in this + * direction is located between this vertex number and the next number in + * sequence. + * @returns The number for the first topological vertex, or INVALID_VERTEX_NUM + * if the direction is not valid for this cell + */ +int vertexNumForDirection(const H3Index origin, const Direction direction) { + int isPent = H3_EXPORT(isPentagon)(origin); + // Check for invalid directions + if (direction == CENTER_DIGIT || direction >= INVALID_DIGIT || + (isPent && direction == K_AXES_DIGIT)) + return INVALID_VERTEX_NUM; + + // Determine the vertex rotations for this cell + int rotations; + H3Error err = vertexRotations(origin, &rotations); + if (err) { + return INVALID_VERTEX_NUM; + } + + // Find the appropriate vertex, rotating CCW if necessary + if (isPent) { + return (directionToVertexNumPent[direction] + NUM_PENT_VERTS - + rotations) % + NUM_PENT_VERTS; + } else { + return (directionToVertexNumHex[direction] + NUM_HEX_VERTS - + rotations) % + NUM_HEX_VERTS; + } +} + +/** @brief Vertex number to hexagon direction relationships (same face). + */ +static const Direction vertexNumToDirectionHex[NUM_HEX_VERTS] = { + IJ_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, + K_AXES_DIGIT, IK_AXES_DIGIT, I_AXES_DIGIT}; + +/** @brief Vertex number to pentagon direction relationships (same face). + */ +static const Direction vertexNumToDirectionPent[NUM_PENT_VERTS] = { + IJ_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, IK_AXES_DIGIT, I_AXES_DIGIT}; + +/** + * Get the direction for a given vertex number. This returns the direction for + * the neighbor between the given vertex number and the next number in sequence. + * @returns The direction for this vertex, or INVALID_DIGIT if the vertex + * number is invalid. + */ +Direction directionForVertexNum(const H3Index origin, const int vertexNum) { + int isPent = H3_EXPORT(isPentagon)(origin); + // Check for invalid vertexes + if (vertexNum < 0 || + vertexNum > (isPent ? NUM_PENT_VERTS : NUM_HEX_VERTS) - 1) + return INVALID_DIGIT; + + // Determine the vertex rotations for this cell + int rotations; + H3Error err = vertexRotations(origin, &rotations); + if (err) { + return INVALID_DIGIT; + } + + // Find the appropriate direction, rotating CW if necessary + return isPent ? vertexNumToDirectionPent[(vertexNum + rotations) % + NUM_PENT_VERTS] + : vertexNumToDirectionHex[(vertexNum + rotations) % + NUM_HEX_VERTS]; +} + +/** @brief Directions in CCW order */ +static const Direction DIRECTIONS[NUM_HEX_VERTS] = { + J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, + IK_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT}; + +/** @brief Reverse direction from neighbor in each direction, + * given as an index into DIRECTIONS to facilitate rotation + */ +static const int revNeighborDirectionsHex[NUM_DIGITS] = { + INVALID_DIGIT, 5, 3, 4, 1, 0, 2}; + +/** + * Get a single vertex for a given cell, as an H3 index, or + * H3_NULL if the vertex is invalid + * @param cell Cell to get the vertex for + * @param vertexNum Number (index) of the vertex to calculate + */ +H3Error H3_EXPORT(cellToVertex)(H3Index cell, int vertexNum, H3Index *out) { + int cellIsPentagon = H3_EXPORT(isPentagon)(cell); + int cellNumVerts = cellIsPentagon ? NUM_PENT_VERTS : NUM_HEX_VERTS; + int res = H3_GET_RESOLUTION(cell); + + // Check for invalid vertexes + if (vertexNum < 0 || vertexNum > cellNumVerts - 1) return E_DOMAIN; + + // Default the owner and vertex number to the input cell + H3Index owner = cell; + int ownerVertexNum = vertexNum; + + // Determine the owner, looking at the three cells that share the vertex. + // By convention, the owner is the cell with the lowest numerical index. + + // If the cell is the center child of its parent, it will always have + // the lowest index of any neighbor, so we can skip determining the owner + if (res == 0 || H3_GET_INDEX_DIGIT(cell, res) != CENTER_DIGIT) { + // Get the left neighbor of the vertex, with its rotations + Direction left = directionForVertexNum(cell, vertexNum); + if (left == INVALID_DIGIT) return E_FAILED; + int lRotations = 0; + H3Index leftNeighbor; + H3Error leftNeighborError = + h3NeighborRotations(cell, left, &lRotations, &leftNeighbor); + if (leftNeighborError) return leftNeighborError; + // Set to owner if lowest index + if (leftNeighbor < owner) owner = leftNeighbor; + + // As above, skip the right neighbor if the left is known lowest + if (res == 0 || H3_GET_INDEX_DIGIT(leftNeighbor, res) != CENTER_DIGIT) { + // Get the right neighbor of the vertex, with its rotations + // Note that vertex - 1 is the right side, as vertex numbers are CCW + Direction right = directionForVertexNum( + cell, (vertexNum - 1 + cellNumVerts) % cellNumVerts); + // This case should be unreachable; invalid verts fail earlier + if (NEVER(right == INVALID_DIGIT)) return E_FAILED; + int rRotations = 0; + H3Index rightNeighbor; + H3Error rightNeighborError = + h3NeighborRotations(cell, right, &rRotations, &rightNeighbor); + if (rightNeighborError) return rightNeighborError; + // Set to owner if lowest index + if (rightNeighbor < owner) { + owner = rightNeighbor; + Direction dir = + H3_EXPORT(isPentagon)(owner) + ? directionForNeighbor(owner, cell) + : DIRECTIONS[(revNeighborDirectionsHex[right] + + rRotations) % + NUM_HEX_VERTS]; + ownerVertexNum = vertexNumForDirection(owner, dir); + } + } + + // Determine the vertex number for the left neighbor + if (owner == leftNeighbor) { + int ownerIsPentagon = H3_EXPORT(isPentagon)(owner); + Direction dir = + ownerIsPentagon + ? directionForNeighbor(owner, cell) + : DIRECTIONS[(revNeighborDirectionsHex[left] + lRotations) % + NUM_HEX_VERTS]; + + // For the left neighbor, we need the second vertex of the + // edge, which may involve looping around the vertex nums + ownerVertexNum = vertexNumForDirection(owner, dir) + 1; + if (ownerVertexNum == NUM_HEX_VERTS || + (ownerIsPentagon && ownerVertexNum == NUM_PENT_VERTS)) { + ownerVertexNum = 0; + } + } + } + + // Create the vertex index + H3Index vertex = owner; + H3_SET_MODE(vertex, H3_VERTEX_MODE); + H3_SET_RESERVED_BITS(vertex, ownerVertexNum); + *out = vertex; + + return E_SUCCESS; +} + +/** + * Get all vertexes for the given cell + * @param cell Cell to get the vertexes for + * @param vertexes Array to hold vertex output. Must have length >= 6. + */ +H3Error H3_EXPORT(cellToVertexes)(H3Index cell, H3Index *vertexes) { + // Get all vertexes. If the cell is a pentagon, will fill the final slot + // with H3_NULL. + bool isPent = H3_EXPORT(isPentagon)(cell); + for (int i = 0; i < NUM_HEX_VERTS; i++) { + if (i == 5 && isPent) { + vertexes[i] = H3_NULL; + } else { + H3Error cellError = H3_EXPORT(cellToVertex)(cell, i, &vertexes[i]); + if (cellError) { + return cellError; + } + } + } + return E_SUCCESS; +} + +/** + * Get the geocoordinates of an H3 vertex + * @param vertex H3 index describing a vertex + * @param coord Output geo coordinate + */ +H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *coord) { + // Get the vertex number and owner from the vertex + int vertexNum = H3_GET_RESERVED_BITS(vertex); + H3Index owner = vertex; + H3_SET_MODE(owner, H3_CELL_MODE); + H3_SET_RESERVED_BITS(owner, 0); + + // Get the single vertex from the boundary + CellBoundary gb; + FaceIJK fijk; + H3Error fijkError = _h3ToFaceIjk(owner, &fijk); + if (fijkError) { + return fijkError; + } + int res = H3_GET_RESOLUTION(owner); + + if (H3_EXPORT(isPentagon)(owner)) { + _faceIjkPentToCellBoundary(&fijk, res, vertexNum, 1, &gb); + } else { + _faceIjkToCellBoundary(&fijk, res, vertexNum, 1, &gb); + } + + // Copy from boundary to output coord + *coord = gb.verts[0]; + return E_SUCCESS; +} + +/** + * Whether the input is a valid H3 vertex + * @param vertex H3 index possibly describing a vertex + * @return Whether the input is valid + */ +int H3_EXPORT(isValidVertex)(H3Index vertex) { + if (H3_GET_MODE(vertex) != H3_VERTEX_MODE) { + return 0; + } + + int vertexNum = H3_GET_RESERVED_BITS(vertex); + H3Index owner = vertex; + H3_SET_MODE(owner, H3_CELL_MODE); + H3_SET_RESERVED_BITS(owner, 0); + + if (!H3_EXPORT(isValidCell)(owner)) { + return 0; + } + + // The easiest way to ensure that the owner + vertex number is valid, + // and that the vertex is canonical, is to recreate and compare. + H3Index canonical; + if (H3_EXPORT(cellToVertex)(owner, vertexNum, &canonical)) { + return 0; + } + + return vertex == canonical ? 1 : 0; +} diff --git a/src/h3lib/lib/vertexGraph.c b/src/h3lib/lib/vertexGraph.c new file mode 100644 index 0000000..fd7c529 --- /dev/null +++ b/src/h3lib/lib/vertexGraph.c @@ -0,0 +1,220 @@ +/* + * Copyright 2017-2018, 2020-2021 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** @file vertexGraph.c + * @brief Data structure for storing a graph of vertices + */ + +#include "vertexGraph.h" + +#include +#include +#include +#include +#include + +#include "alloc.h" +#include "latLng.h" + +/** + * Initialize a new VertexGraph + * @param graph Graph to initialize + * @param numBuckets Number of buckets to include in the graph + * @param res Resolution of the hexagons whose vertices we're storing + */ +void initVertexGraph(VertexGraph *graph, int numBuckets, int res) { + if (numBuckets > 0) { + graph->buckets = H3_MEMORY(calloc)(numBuckets, sizeof(VertexNode *)); + assert(graph->buckets != NULL); + } else { + graph->buckets = NULL; + } + graph->numBuckets = numBuckets; + graph->size = 0; + graph->res = res; +} + +/** + * Destroy a VertexGraph's sub-objects, freeing their memory. The caller is + * responsible for freeing memory allocated to the VertexGraph struct itself. + * @param graph Graph to destroy + */ +void destroyVertexGraph(VertexGraph *graph) { + VertexNode *node; + while ((node = firstVertexNode(graph)) != NULL) { + removeVertexNode(graph, node); + } + H3_MEMORY(free)(graph->buckets); +} + +/** + * Get an integer hash for a lat/lng point, at a precision determined + * by the current hexagon resolution. + * TODO: Light testing suggests this might not be sufficient at resolutions + * finer than 10. Design a better hash function if performance and collisions + * seem to be an issue here. + * @param vertex Lat/lng vertex to hash + * @param res Resolution of the hexagon the vertex belongs to + * @param numBuckets Number of buckets in the graph + * @return Integer hash + */ +uint32_t _hashVertex(const LatLng *vertex, int res, int numBuckets) { + // Simple hash: Take the sum of the lat and lng with a precision level + // determined by the resolution, converted to int, modulo bucket count. + return (uint32_t)fmod(fabs((vertex->lat + vertex->lng) * pow(10, 15 - res)), + numBuckets); +} + +void _initVertexNode(VertexNode *node, const LatLng *fromVtx, + const LatLng *toVtx) { + node->from = *fromVtx; + node->to = *toVtx; + node->next = NULL; +} + +/** + * Add a edge to the graph + * @param graph Graph to add node to + * @param fromVtx Start vertex + * @param toVtx End vertex + * @return Pointer to the new node + */ +VertexNode *addVertexNode(VertexGraph *graph, const LatLng *fromVtx, + const LatLng *toVtx) { + // Make the new node + VertexNode *node = H3_MEMORY(malloc)(sizeof(VertexNode)); + assert(node != NULL); + _initVertexNode(node, fromVtx, toVtx); + // Determine location + uint32_t index = _hashVertex(fromVtx, graph->res, graph->numBuckets); + // Check whether there's an existing node in that spot + VertexNode *currentNode = graph->buckets[index]; + if (currentNode == NULL) { + // Set bucket to the new node + graph->buckets[index] = node; + } else { + // Find the end of the list + do { + // Check the the edge we're adding doesn't already exist + if (geoAlmostEqual(¤tNode->from, fromVtx) && + geoAlmostEqual(¤tNode->to, toVtx)) { + // already exists, bail + H3_MEMORY(free)(node); + return currentNode; + } + if (currentNode->next != NULL) { + currentNode = currentNode->next; + } + } while (currentNode->next != NULL); + // Add the new node to the end of the list + currentNode->next = node; + } + graph->size++; + return node; +} + +/** + * Remove a node from the graph. The input node will be freed, and should + * not be used after removal. + * @param graph Graph to mutate + * @param node Node to remove + * @return 0 on success, 1 on failure (node not found) + */ +int removeVertexNode(VertexGraph *graph, VertexNode *node) { + // Determine location + uint32_t index = _hashVertex(&node->from, graph->res, graph->numBuckets); + VertexNode *currentNode = graph->buckets[index]; + int found = 0; + if (currentNode != NULL) { + if (currentNode == node) { + graph->buckets[index] = node->next; + found = 1; + } + // Look through the list + while (!found && currentNode->next != NULL) { + if (currentNode->next == node) { + // splice the node out + currentNode->next = node->next; + found = 1; + } + currentNode = currentNode->next; + } + } + if (found) { + H3_MEMORY(free)(node); + graph->size--; + return 0; + } + // Failed to find the node + return 1; +} + +/** + * Find the Vertex node for a given edge, if it exists + * @param graph Graph to look in + * @param fromVtx Start vertex + * @param toVtx End vertex, or NULL if we don't care + * @return Pointer to the vertex node, if found + */ +VertexNode *findNodeForEdge(const VertexGraph *graph, const LatLng *fromVtx, + const LatLng *toVtx) { + // Determine location + uint32_t index = _hashVertex(fromVtx, graph->res, graph->numBuckets); + // Check whether there's an existing node in that spot + VertexNode *node = graph->buckets[index]; + if (node != NULL) { + // Look through the list and see if we find the edge + do { + if (geoAlmostEqual(&node->from, fromVtx) && + (toVtx == NULL || geoAlmostEqual(&node->to, toVtx))) { + return node; + } + node = node->next; + } while (node != NULL); + } + // Iteration lookup fail + return NULL; +} + +/** + * Find a Vertex node starting at the given vertex + * @param graph Graph to look in + * @param fromVtx Start vertex + * @return Pointer to the vertex node, if found + */ +VertexNode *findNodeForVertex(const VertexGraph *graph, const LatLng *fromVtx) { + return findNodeForEdge(graph, fromVtx, NULL); +} + +/** + * Get the next vertex node in the graph. + * @param graph Graph to iterate + * @return Vertex node, or NULL if at the end + */ +VertexNode *firstVertexNode(const VertexGraph *graph) { + VertexNode *node = NULL; + int currentIndex = 0; + while (node == NULL) { + if (currentIndex < graph->numBuckets) { + // find the first node in the next bucket + node = graph->buckets[currentIndex]; + } else { + // end of iteration + return NULL; + } + currentIndex++; + } + return node; +} diff --git a/src/main.cpp b/src/main.cpp index 9db0b25..a00d615 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,11 @@ #include "h3api.h" #include "cubao/polyline_ruler.hpp" #include "spdlog/spdlog.h" +// fix exposed macro 'GetObject' from wingdi.h (included by spdlog.h) under +// windows, see https://github.com/Tencent/rapidjson/issues/1448 +#ifdef GetObject +#undef GetObject +#endif #include #include @@ -37,8 +42,9 @@ namespace py = pybind11; using namespace pybind11::literals; -using namespace cubao; +namespace cubao +{ using RapidjsonValue = mapbox::geojson::rapidjson_value; using RapidjsonAllocator = mapbox::geojson::rapidjson_allocator; using RapidjsonDocument = mapbox::geojson::rapidjson_document; @@ -179,9 +185,9 @@ inline void index_geometry(int index, const mapbox::geojson::geometry &geom, { geom.match( [&](const mapbox::geojson::line_string &ls) { - auto llas = douglas_simplify( - Eigen::Map(&ls[0].x, ls.size(), 3), 1.0, - true); + RowVectors src = + Eigen::Map(&ls[0].x, ls.size(), 3); + auto llas = douglas_simplify(src, 1.0, true); positions.push_back(llas.row(llas.rows() / 2)); polylines.emplace(index, std::move(llas)); }, @@ -197,25 +203,25 @@ inline void index_geometry(int index, const mapbox::geojson::geometry &geom, }, [&](const mapbox::geojson::polygon &g) { auto &ls = g[0]; - auto llas = douglas_simplify( - Eigen::Map(&ls[0].x, ls.size(), 3), 1.0, - true); + RowVectors src = + Eigen::Map(&ls[0].x, ls.size(), 3); + auto llas = douglas_simplify(src, 1.0, true); positions.push_back(llas.row(llas.rows() / 2)); polylines.emplace(index, std::move(llas)); }, [&](const mapbox::geojson::multi_line_string &g) { auto &ls = g[0]; - auto llas = douglas_simplify( - Eigen::Map(&ls[0].x, ls.size(), 3), 1.0, - true); + RowVectors src = + Eigen::Map(&ls[0].x, ls.size(), 3); + auto llas = douglas_simplify(src, 1.0, true); positions.push_back(llas.row(llas.rows() / 2)); polylines.emplace(index, std::move(llas)); }, [&](const mapbox::geojson::multi_polygon &g) { auto &ls = g[0][0]; - auto llas = douglas_simplify( - Eigen::Map(&ls[0].x, ls.size(), 3), 1.0, - true); + RowVectors src = + Eigen::Map(&ls[0].x, ls.size(), 3); + auto llas = douglas_simplify(src, 1.0, true); positions.push_back(llas.row(llas.rows() / 2)); polylines.emplace(index, std::move(llas)); }, @@ -459,9 +465,10 @@ strip_geojson(const mapbox::geojson::feature_collection &_features, RapidjsonValue properties(rapidjson::kObjectType); f.geometry.match( [&](const mapbox::geojson::line_string &ls) { - auto llas = douglas_simplify( - Eigen::Map(&ls[0].x, ls.size(), 3), - options.douglas_epsilon, true); + RowVectors src = + Eigen::Map(&ls[0].x, ls.size(), 3); + auto llas = + douglas_simplify(src, options.douglas_epsilon, true); mapbox::geojson::line_string geom; geom.resize(llas.rows()); Eigen::Map(&geom[0].x, geom.size(), 3) = llas; @@ -746,23 +753,32 @@ bool dissect_geojson(const std::string &input_path, } return true; } +} // namespace cubao -PYBIND11_MODULE(pybind11_geocondense, m) +PYBIND11_MODULE(_core, m) { + using namespace cubao; py::class_(m, "CondenseOptions", py::module_local()) // - .def(py::init<>()) - .def_readwrite("douglas_epsilon", &CondenseOptions::douglas_epsilon) + .def(py::init<>(), "Default constructor for CondenseOptions") + .def_readwrite("douglas_epsilon", &CondenseOptions::douglas_epsilon, + "Epsilon value for Douglas-Peucker algorithm") .def_readwrite("grid_h3_resolution", - &CondenseOptions::grid_h3_resolution) - .def_readwrite("indent", &CondenseOptions::indent) - .def_readwrite("sort_keys", &CondenseOptions::sort_keys) + &CondenseOptions::grid_h3_resolution, + "H3 resolution for grid features") + .def_readwrite("indent", &CondenseOptions::indent, + "Indentation option for JSON output") + .def_readwrite("sort_keys", &CondenseOptions::sort_keys, + "Option to sort keys in JSON output") .def_readwrite("grid_features_keep_properties", - &CondenseOptions::grid_features_keep_properties) + &CondenseOptions::grid_features_keep_properties, + "Option to keep properties for grid features") .def_readwrite("sparsify_h3_resolution", - &CondenseOptions::sparsify_h3_resolution) + &CondenseOptions::sparsify_h3_resolution, + "H3 resolution for sparsification") .def_readwrite("sparsify_upper_limit", - &CondenseOptions::sparsify_upper_limit) - .def_readwrite("debug", &CondenseOptions::debug) + &CondenseOptions::sparsify_upper_limit, + "Upper limit for sparsification") + .def_readwrite("debug", &CondenseOptions::debug, "Debug option") // ; @@ -772,7 +788,20 @@ PYBIND11_MODULE(pybind11_geocondense, m) "output_index_path"_a = std::nullopt, // "output_strip_path"_a = std::nullopt, // "output_grids_dir"_a = std::nullopt, // - "options"_a = CondenseOptions()) + "options"_a = CondenseOptions(), // + R"docstring( + Condense GeoJSON data. + + Args: + input_path: Path to the input GeoJSON file. + output_index_path: Optional path for the output index file. + output_strip_path: Optional path for the output strip file. + output_grids_dir: Optional directory for output grid files. + options: CondenseOptions object with configuration options. + + Returns: + bool: True if the operation was successful, False otherwise. + )docstring") // ; @@ -783,7 +812,21 @@ PYBIND11_MODULE(pybind11_geocondense, m) "output_properties"_a = std::nullopt, // "output_observations"_a = std::nullopt, // "output_others"_a = std::nullopt, // - "indent"_a = false) + "indent"_a = false, // + R"docstring( + Dissect GeoJSON data into separate components. + + Args: + input_path: Path to the input GeoJSON file. + output_geometry: Optional path for the output geometry file. + output_properties: Optional path for the output properties file. + output_observations: Optional path for the output observations file. + output_others: Optional path for other output data. + indent: Boolean flag to enable indentation in output JSON. + + Returns: + bool: True if the operation was successful, False otherwise. + )docstring") // ; diff --git a/tests/test_basic.py b/tests/test_basic.py index a8e2c66..47eda6e 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,6 +1,6 @@ import os # noqa -from geocondense import CondenseOptions, __version__, condense_geojson # noqa +from geocondense import __version__ def test_dummy():