pax_global_header00006660000000000000000000000064147523471560014530gustar00rootroot0000000000000052 comment=f7ae65e69b2f46d5197677b90a81986482fa0293 h3-pg-4.2.2/000077500000000000000000000000001475234715600124535ustar00rootroot00000000000000h3-pg-4.2.2/.github/000077500000000000000000000000001475234715600140135ustar00rootroot00000000000000h3-pg-4.2.2/.github/FUNDING.yml000066400000000000000000000013401475234715600156260ustar00rootroot00000000000000# These are supported funding model platforms github: [zachasme] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] h3-pg-4.2.2/.github/workflows/000077500000000000000000000000001475234715600160505ustar00rootroot00000000000000h3-pg-4.2.2/.github/workflows/documentation.yml000066400000000000000000000005721475234715600214500ustar00rootroot00000000000000name: documentation on: push: branches: [main] pull_request: branches: [main] jobs: documentation: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.10" - run: pipx install poetry - run: scripts/documentation - run: git diff --exit-code docs h3-pg-4.2.2/.github/workflows/pgxn.yml000066400000000000000000000016051475234715600175510ustar00rootroot00000000000000name: pgxn on: push: branches: [main] pull_request: branches: [main] jobs: pgxn: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install PGXN Client run: sudo pip install pgxnclient - name: Install PostgreSQL server dependencies run: | sudo apt-get update sudo apt-get install postgresql-server-dev-14 postgis - name: Prepare PostgreSQL run: | sudo systemctl start postgresql.service sudo -u postgres -i createuser --superuser runner sudo -u postgres -i createdb runner psql -c "CREATE EXTENSION postgis; CREATE EXTENSION postgis_raster;" - name: Bundle run: scripts/bundle - name: Install run: sudo pgxn install ./h3-*.zip - name: Load run: pgxn load h3 - name: Check run: pgxn check h3 h3-pg-4.2.2/.github/workflows/test-linux.yml000066400000000000000000000027321475234715600207130ustar00rootroot00000000000000name: test-linux on: push: branches: [main] pull_request: branches: [main] jobs: linux: strategy: matrix: os: [ubuntu-24.04, ubuntu-22.04] pg: [17, 16, 15, 14, 13] config: [Release, Debug] runs-on: ${{ matrix.os }} name: ${{ matrix.os }} 🐘${{ matrix.pg }} (${{ matrix.config }}) steps: - uses: actions/checkout@v4 - name: Setup pg_validate_extupgrade uses: baptiste0928/cargo-install@v3 with: crate: pg_validate_extupgrade git: https://github.com/rjuju/pg_validate_extupgrade - name: Setup PostgreSQL run: | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -p -i -v ${{ matrix.pg }} sudo apt-get -y install postgresql-${{ matrix.pg }}-postgis-3 sudo -u postgres -i createuser --superuser runner sudo -u postgres -i createdb runner psql -c "CREATE EXTENSION postgis; CREATE EXTENSION postgis_raster;" - name: Generate run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config }} - name: Build run: cmake --build build - name: Install run: sudo cmake --install build --component h3-pg - name: Test env: PGHOST: /var/run/postgresql run: ctest --test-dir build --output-on-failure --build-config ${{ matrix.config }} - name: Print regression diffs if: ${{ failure() }} run: cat build/*/test/regression.diffs h3-pg-4.2.2/.github/workflows/test-macos.yml000066400000000000000000000034461475234715600206610ustar00rootroot00000000000000name: test-macos on: push: branches: [main] pull_request: branches: [main] jobs: macos: strategy: matrix: os: [macos-13] pg: [17, 16, 15, 14, 13] config: [Release, Debug] runs-on: ${{ matrix.os }} name: ${{ matrix.os }} 🐘${{ matrix.pg }} (${{ matrix.config }}) steps: - uses: actions/checkout@v4 - name: Setup pg_validate_extupgrade uses: baptiste0928/cargo-install@v3 with: crate: pg_validate_extupgrade git: https://github.com/rjuju/pg_validate_extupgrade - name: Setup PostgreSQL run: | brew install postgresql@${{ matrix.pg }} brew unlink postgresql@${{ matrix.pg }} brew link --overwrite postgresql@${{ matrix.pg }} brew services run postgresql@${{ matrix.pg }} - name: Setup PostGIS run: brew install postgis if: ${{ matrix.pg == 17 || matrix.pg == 14 }} - name: Fix gettext linkage for PostgreSQL >= 15 run: brew unlink gettext && brew link --overwrite --force gettext if: ${{ matrix.pg >= 15 }} - name: Generate run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config }} env: # Fixes missing libintl.h on some runners CFLAGS: "-I /usr/local/include" - name: Build run: cmake --build build --config ${{ matrix.config }} - name: Install run: sudo cmake --install build --component h3-pg --config ${{ matrix.config }} - name: Create test database (for pg_validate_extupgrade) run: createdb runner - name: Test run: ctest --test-dir build --output-on-failure --build-config ${{ matrix.config }} - name: Print regression diffs if: ${{ failure() }} run: cat build/*/regression.diffs h3-pg-4.2.2/.github/workflows/test-windows.yml000066400000000000000000000015321475234715600212430ustar00rootroot00000000000000name: test-windows on: push: branches: [main] pull_request: branches: [main] jobs: windows: strategy: matrix: config: [Release, Debug] runs-on: windows-2022 steps: - uses: actions/checkout@v4 - name: Fix default PostgreSQL version on Windows Runner run: $env:PGBIN >> $env:GITHUB_PATH - name: Generate run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config }} - name: Build run: cmake --build build --config ${{ matrix.config }} - name: Install run: cmake --install build --component h3-pg --config ${{ matrix.config }} - name: Test run: ctest --test-dir build --output-on-failure --build-config ${{ matrix.config }} - name: Print regression diffs if: ${{ failure() }} run: cat build/*/test/regression.diffs h3-pg-4.2.2/.gitignore000066400000000000000000000003321475234715600144410ustar00rootroot00000000000000build*/ # C *.o *.bc *.so # Test results h3/test/regression.* h3/test/results # Generated /h3*.sql /h3/test/expected/ci-*.out /h3/test/sql/ci-*.sql /libh3-*/ /h3-*.zip *.BAK # IDEs .vscode/* !.vscode/settings.json h3-pg-4.2.2/.vscode/000077500000000000000000000000001475234715600140145ustar00rootroot00000000000000h3-pg-4.2.2/.vscode/settings.json000066400000000000000000000000311475234715600165410ustar00rootroot00000000000000{ "editor.tabSize": 4 }h3-pg-4.2.2/CHANGELOG.md000066400000000000000000000300721475234715600142660ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. Critical bugfixes or breaking changes are marked using a warning symbol: ⚠️ _The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)._ ## Versioning The H3 core library adheres to [Semantic Versioning](http://semver.org/). H3-pg has a `major.minor.patch` version scheme. The major and minor version numbers of H3-pg are the major and minor version of the bound core library, respectively. The patch version is incremented independently of the core library. Because H3-pg is versioned in lockstep with the H3 core library, please avoid adding features or APIs which do not map onto the [H3 core API](https://uber.github.io/h3/#/documentation/api-reference/). ## [Unreleased]
Changes that have landed in master but are not yet released. Click to see more.
## [4.2.2] - 2025-02-10 - More upstream copy/paste in support of Debian package (see [#169], thanks [@df7cb]) ## [4.2.1] - 2025-02-04 - Copy `h3Index.h` from upstream in support of Debian package (see [#169], thanks [@df7cb]) ## [4.2.0] - 2025-01-17 - Bump `h3` to `v4.2.0` - Add `h3_polygon_to_cells_experimental` (see [#159], thanks [@jmealo]) - Add experimental SP-GIST operator class (see [#43], thanks [@BielStela]) - Fix for MacOS in nixpkgs / NixOS (see [#141], thanks [@wolfgangwalther]) ## [4.1.4] - 2024-11-06 - Fix library extension on macOS for PostgreSQL >= 16 (see [#140], thanks [@bayandin]) - Add support for binary I/O (see [#160], thanks [@robertozimek]) ## [4.1.3] - 2023-07-26 - Add `geometry @ int` and `geography @ int` operators, as shortcuts for `h3_lat_lng_to_cell`. - Explain PostGIS SRID expectations (see [#131], thanks [@rustprooflabs]) ## [4.1.2] - 2023-02-08 - *Actually* fix `h3_postgis` upgrade path (see [#117], thanks [@mngr777]) ## [4.1.1] - 2023-02-07 - Add `postgis_raster` integration (see [#112], thanks [@mngr777]) - Fix `h3_postgis` upgrade path (see [#117], thanks [@mngr777]) ## [4.1.0] - 2023-01-18 - Bump `h3` to `v4.1.0` - Add bindings for `h3_cell_to_child_pos` and `h3_child_pos_to_cell`. - Use CMake for entire build (see [#70]) - Add helper to fix pass-by-value migration (see [#111]) - Allow distance operator `<->` to work for cells at different resolutions (using center child). ## [4.0.3] - 2022-11-04 - Add BRIN operator class (see [#97], thanks [@mngr777]) - Split PostGIS multipolygons by 180th meridian (see [#90], thanks [@mngr777]) - Add aggregate functions for `h3_postgis` (see [#91], thanks [@mngr777]) - Add recursive `h3_grid_path_cells` (see [#93], thanks [@mngr777]) ## [4.0.2] - 2022-09-19 - Fix broken function renames (see [#87], thanks [@Kompza], [@mngr777]) ## [4.0.1] - 2022-09-19 - Bump `h3` to `v4.0.1` (was locked on `rc5` for the previous release) - Add compile flag support check (see [#78], thanks [@mngr777]) - Split polygons by 180th meridian (see [#76], thanks [@mngr777]) ## [4.0.0] - 2022-08-24 - ⚠️ Update `h3` core library to `v4.0.0`. Almost all functions have been renamed to align with `h3` core `v4`. Please take care when updating. You can see a [list of changed function names](https://h3geo.org/docs/library/migration-3.x/functions) in the core library documentation. - ⚠️ The PostGIS functions has been extracted into a separate extension `h3_postgis`. Install using `CREATE EXTENSION h3_postgis;`. - Enable link time optimization (see [#75], thanks [@mngr777]) - Handle non-polygons for `h3_polyfill` (see [#55], thanks [@Lokks]) - Take advantage of the new v4 error codes (fixes [#71], thanks [@kalenikaliaksandr]) ## [3.7.2] - 2022-04-13 - Allow `NULL` in `holes` array for `h3_polyfill` (see [#64], thanks [@mngr777]) - Allow >1Gb memory allocations for `h3_polyfill` (see [#65], thanks [@mngr777]) ## [3.7.1] - 2021-06-23 - Update `h3` core library to `v3.7.1` ## [3.7.0] - 2020-09-30 - ⚠️ Default unit for `h3_hex_area` and `h3_edge_length` changed to kilometers - Update `h3` core library to `v3.7.0` - Add `h3_point_dist` and `h3_exact_edge_length` bindings - Add distance operator `<->` - Fix `h3_to_geography` and `h3_to_geometry` refering to removed functions if extension was upgraded from pre-1.0 - Add optional input validation in geoToH3 (see [#41], thanks [@trylinka]) - Support unit as string argument in `h3_hex_area` and `h3_edge_length` (and flag previous version for deprecation) ## [3.6.5] - 2020-08-14 - Add support for partitioning by hash (see [#37], thanks [@abelvm]) - Fix difference in function flags between fresh install and upgrades (see [#38], thanks [@abelvm]) ## [3.6.4] - 2020-06-29 - Update `h3` core library to `v3.6.4` ## [3.6.3] - 2020-04-08 - Build `h3` core using release flag for 2x/3x performance (see [#23], thanks [@komzpa]) ## [3.6.2] - 2020-04-07 **This update will corrupt your h3indexes unless your are using a 32-bit build of PostgreSQL (see [#31]). If you upgrade to `4.0.4` you can use the function `h3_pg_migrate_pass_by_reference(h3index)` to retrieve your old h3 cells. See [#111].** - Add parallel safety flags to PostGIS functions (see [#19], thanks [@komzpa]) - Add B-Tree sort support (see [#24], thanks [@komzpa]) - ⚠️ Make type `h3index` pass-by-value on supported systems (see [#22], [#26], thanks [@komzpa]) - Update `h3` core library to `v3.6.3` ## [3.6.1] - 2019-12-09 - Add `&&`, `@>` and `<@` operators for overlaps, contains and contained by respectively - Fix PostgreSQL 12 build (see [#18], thanks [@komzpa]) - Update `h3` core library to `v3.6.1` ## [3.6.0] - 2019-10-07 - Add support for `bigint` cast (see [#9], thanks [@kmacdough]) - Add `h3_to_center_child` binding - Add `h3_get_pentagon_indexes` binding - Update `h3` core library to `v3.6.0` ## [3.5.0] - 2019-08-01 - Add `h3_get_faces` binding - ⚠️ Replace `h3_hex_area_m2` and `h3_hex_area_km2` with `h3_hex_area` - ⚠️ Replace `h3_edge_length_m` and `h3_edge_length_km` with `h3_edge_length` - ⚠️ Remove `hex_range`, `hex_ranges` and `hex_range_distances` - Remove `h3` core library version check, since we know which version we are linking - Fix PostgreSQL 12 build (see [#4], thanks [@komzpa]) - Update `h3` core library to `v3.5.0` ## [3.4.1] - 2019-06-14 - Fix `abs` warning ## [3.4.0] - 2019-06-13 - ⚠️ Remove degree/radian conversion helpers (in favor of built-in RADIANS/DEGREES) ## [1.0.6] - 2019-06-03 - Update `h3` core library to `v3.4.4` ## [1.0.5] - 2019-02-15 - Fix update path ## [1.0.4] - 2019-02-15 - Fix `polyfill` for polygon with multiple holes ## [1.0.3] - 2019-01-27 - Fix update path ## [1.0.2] - 2019-01-27 - Remove git tag check in `distribute` makefile target, since it causes error on pgxn install ## [1.0.1] - 2019-01-27 - Remove usage of `FALSE` instead of 0 in conditional in `ASSERT` macro ## [1.0.0] - 2019-01-27 - Add `h3_get_extension_version()` - Add hash operator class, now `WHERE IN` works - ⚠️ Replace `h3_basecells` with `h3_get_res_0_indexes` - ⚠️ Rename all functions with double `h3_h3_` prefix to use single `h3_` prefix - ⚠️ Remove `h3_haversine_distance` function - ⚠️ Change Makefile such that the `h3` core library is cloned, built and statically linked - Test that upgrade path has same result as fresh install ## [0.4.0] - 2019-01-12 - Add `h3_line` binding - Fix `h3_h3_to_children_slow` ## [0.3.2] - 2019-01-08 - ⚠️ Fix `btree` operator class indexing ## [0.3.1] - 2018-12-17 - Add `extend` flag to `h3_h3_to_geo_boundary` such that polygons are not wrapped at antimeridian ## 0.3.0 - 2018-12-11 - Initial public release [unreleased]: https://github.com/zachasme/h3-pg/compare/v4.2.2...HEAD [4.2.2]: https://github.com/zachasme/h3-pg/compare/v4.2.1...v4.2.2 [4.2.1]: https://github.com/zachasme/h3-pg/compare/v4.2.0...v4.2.1 [4.2.0]: https://github.com/zachasme/h3-pg/compare/v4.1.4...v4.2.0 [4.1.4]: https://github.com/zachasme/h3-pg/compare/v4.1.3...v4.1.4 [4.1.3]: https://github.com/zachasme/h3-pg/compare/v4.1.2...v4.1.3 [4.1.2]: https://github.com/zachasme/h3-pg/compare/v4.1.1...v4.1.2 [4.1.1]: https://github.com/zachasme/h3-pg/compare/v4.1.0...v4.1.1 [4.1.0]: https://github.com/zachasme/h3-pg/compare/v4.0.3...v4.1.0 [4.0.3]: https://github.com/zachasme/h3-pg/compare/v4.0.2...v4.0.3 [4.0.2]: https://github.com/zachasme/h3-pg/compare/v4.0.1...v4.0.2 [4.0.1]: https://github.com/zachasme/h3-pg/compare/v4.0.0...v4.0.1 [4.0.0]: https://github.com/zachasme/h3-pg/compare/v3.7.2...v4.0.0 [3.7.2]: https://github.com/zachasme/h3-pg/compare/v3.7.1...v3.7.2 [3.7.1]: https://github.com/zachasme/h3-pg/compare/v3.7.0...v3.7.1 [3.7.0]: https://github.com/zachasme/h3-pg/compare/v3.6.5...v3.7.0 [3.6.5]: https://github.com/zachasme/h3-pg/compare/v3.6.4...v3.6.5 [3.6.4]: https://github.com/zachasme/h3-pg/compare/v3.6.3...v3.6.4 [3.6.3]: https://github.com/zachasme/h3-pg/compare/v3.6.2...v3.6.3 [3.6.2]: https://github.com/zachasme/h3-pg/compare/v3.6.1...v3.6.2 [3.6.1]: https://github.com/zachasme/h3-pg/compare/v3.6.0...v3.6.1 [3.6.0]: https://github.com/zachasme/h3-pg/compare/v3.5.0...v3.6.0 [3.5.0]: https://github.com/zachasme/h3-pg/compare/v3.4.1...v3.5.0 [3.4.1]: https://github.com/zachasme/h3-pg/compare/v3.4.0...v3.4.1 [3.4.0]: https://github.com/zachasme/h3-pg/compare/v1.0.6...v3.4.0 [1.0.6]: https://github.com/zachasme/h3-pg/compare/v1.0.5...v1.0.6 [1.0.5]: https://github.com/zachasme/h3-pg/compare/v1.0.4...v1.0.5 [1.0.4]: https://github.com/zachasme/h3-pg/compare/v1.0.3...v1.0.4 [1.0.3]: https://github.com/zachasme/h3-pg/compare/v1.0.2...v1.0.3 [1.0.2]: https://github.com/zachasme/h3-pg/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/zachasme/h3-pg/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/zachasme/h3-pg/compare/v0.4.0...v1.0.0 [0.4.0]: https://github.com/zachasme/h3-pg/compare/v0.3.2...v0.4.0 [0.3.2]: https://github.com/zachasme/h3-pg/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/zachasme/h3-pg/compare/v0.3.0...v0.3.1 [#4]: https://github.com/zachasme/h3-pg/pull/4 [#9]: https://github.com/zachasme/h3-pg/pull/9 [#18]: https://github.com/zachasme/h3-pg/pull/18 [#19]: https://github.com/zachasme/h3-pg/pull/19 [#22]: https://github.com/zachasme/h3-pg/pull/22 [#23]: https://github.com/zachasme/h3-pg/issues/23 [#24]: https://github.com/zachasme/h3-pg/pull/24 [#26]: https://github.com/zachasme/h3-pg/pull/26 [#31]: https://github.com/zachasme/h3-pg/pull/31 [#37]: https://github.com/zachasme/h3-pg/issues/37 [#38]: https://github.com/zachasme/h3-pg/issues/38 [#41]: https://github.com/zachasme/h3-pg/issues/41 [#43]: https://github.com/zachasme/h3-pg/issues/43 [#55]: https://github.com/zachasme/h3-pg/issues/55 [#64]: https://github.com/zachasme/h3-pg/issues/64 [#65]: https://github.com/zachasme/h3-pg/pull/65 [#70]: https://github.com/zachasme/h3-pg/pull/70 [#71]: https://github.com/zachasme/h3-pg/issues/71 [#75]: https://github.com/zachasme/h3-pg/pull/75 [#76]: https://github.com/zachasme/h3-pg/pull/76 [#78]: https://github.com/zachasme/h3-pg/pull/78 [#87]: https://github.com/zachasme/h3-pg/pull/87 [#90]: https://github.com/zachasme/h3-pg/pull/90 [#91]: https://github.com/zachasme/h3-pg/pull/91 [#93]: https://github.com/zachasme/h3-pg/pull/93 [#97]: https://github.com/zachasme/h3-pg/pull/97 [#111]: https://github.com/zachasme/h3-pg/pull/111 [#112]: https://github.com/zachasme/h3-pg/pull/112 [#117]: https://github.com/zachasme/h3-pg/issues/117 [#131]: https://github.com/zachasme/h3-pg/pull/131 [#140]: https://github.com/zachasme/h3-pg/pull/140 [#141]: https://github.com/zachasme/h3-pg/pull/141 [#159]: https://github.com/zachasme/h3-pg/pull/159 [#160]: https://github.com/zachasme/h3-pg/pull/160 [#169]: https://github.com/zachasme/h3-pg/issues/169 [@abelvm]: https://github.com/AbelVM [@bayandin]: https://github.com/bayandin [@BielStela]: https://github.com/BielStela [@df7cb]: https://github.com/df7cb [@jmealo]: https://github.com/jmealo [@kalenikaliaksandr]: https://github.com/kalenikaliaksandr [@kmacdough]: https://github.com/kmacdough [@komzpa]: https://github.com/Komzpa [@lokks]: https://github.com/Lokks [@mngr777]: https://github.com/mngr777 [@robertozimek]: https://github.com/robertozimek [@rustprooflabs]: https://github.com/rustprooflabs [@trylinka]: https://github.com/trylinka [@wolfgangwalther]: https://github.com/wolfgangwalther h3-pg-4.2.2/CITATION.cff000066400000000000000000000004741475234715600143520ustar00rootroot00000000000000cff-version: 1.2.0 message: "If you use this software, please cite it as below." authors: - family-names: "Knudsen" given-names: "Zacharias" orcid: https://orcid.org/0000-0003-3662-8396 title: "h3-pg" version: v3.7.2 doi: 10.5281/zenodo.6856596 date-released: 2022-04-13 url: "https://github.com/zachasme/h3-pg"h3-pg-4.2.2/CMakeLists.txt000066400000000000000000000051521475234715600152160ustar00rootroot00000000000000# Copyright 2022 Zacharias Knudsen # # 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. # Almost all CMake files should start with this # You should always specify a range with the newest # and oldest tested versions of CMake. This will ensure # you pick up the best policies. # Keep minimum version in sync with: # https://github.com/uber/h3/blob/master/CMakeLists.txt cmake_minimum_required(VERSION 3.20..3.24) if(POLICY CMP0135) # Set the timestamps of all extracted contents to the time of the extraction cmake_policy(SET CMP0135 NEW) endif() # This is your project statement. You should always list languages; # Listing the version is nice here since it sets lots of useful variables project( h3-pg VERSION 4.2.2 LANGUAGES C ) # set this to "${PROJECT_VERSION}" on release set(INSTALL_VERSION "${PROJECT_VERSION}") #set(INSTALL_VERSION "unreleased") set(H3_CORE_VERSION 4.2.0) set(H3_CORE_SHA256 438db46fc2b388785d2a0d8e26aa5509739240a7b50b2510c416778d871a4e11) # If you set any CMAKE_ variables, that can go here. # (But usually don't do this, except maybe for C++ standard) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") # Allow forcing PostgreSQL version set(POSTGRESQL_VERSION $ENV{POSTGRESQL_VERSION} CACHE STRING "PostgreSQL version major") # Find packages go here. find_package(PostgreSQL ${POSTGRESQL_VERSION} REQUIRED OPTIONAL_COMPONENTS PostGIS ) include(AddPostgreSQLExtension) add_definitions(-DPOSTGRESQL_VERSION_MAJOR=${PostgreSQL_VERSION_MAJOR}) # https://cliutils.gitlab.io/modern-cmake/chapters/testing.html if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) include(CTest) endif() # Include core library add_subdirectory(cmake/h3) # Include extensions and their shared code add_subdirectory(include) add_subdirectory(h3) add_subdirectory(h3_postgis) # Add target that bundles for pgxn configure_file(META.json.in META.json) add_custom_target(pgxn COMMAND git archive --format zip --prefix="h3-${INSTALL_VERSION}/" --add-file ${CMAKE_BINARY_DIR}/META.json -o h3-${INSTALL_VERSION}.zip HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) h3-pg-4.2.2/LICENSE000077500000000000000000000236751475234715600135000ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS h3-pg-4.2.2/META.json.in000066400000000000000000000020421475234715600144770ustar00rootroot00000000000000{ "name": "h3", "abstract": "PostgreSQL bindings for H3", "description": "PostgreSQL bindings for H3, a hierarchical hexagonal geospatial indexing system.", "version": "@PROJECT_VERSION@", "maintainer": "Zacharias Knudsen ", "license": "apache_2_0", "provides": { "h3": { "abstract": "PostgreSQL bindings for H3", "file": "h3--@PROJECT_VERSION@.sql", "docfile": "README.md", "version": "@PROJECT_VERSION@" }, "h3_postgis": { "abstract": "H3 PostGIS integration", "file": "h3_postgis--@PROJECT_VERSION@.sql", "docfile": "README.md", "version": "@PROJECT_VERSION@" } }, "resources": { "homepage": "https://github.com/zachasme/h3-pg", "bugtracker": { "web": "https://github.com/zachasme/h3-pg/issues" }, "repository": { "url": "https://github.com/zachasme/h3-pg.git", "web": "https://github.com/zachasme/h3-pg", "type": "git" } }, "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" } } h3-pg-4.2.2/Makefile000066400000000000000000000015231475234715600141140ustar00rootroot00000000000000# Copyright 2024 Zacharias Knudsen # # 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. # this file only exists to support pgxnclient .PHONY: all install installcheck all: cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build install: cmake --install build --component h3-pg installcheck: ctest --output-on-failure --build-config Release h3-pg-4.2.2/README.md000066400000000000000000000076061475234715600137430ustar00rootroot00000000000000# h3-pg: Uber's H3 Hexagonal Hierarchical Geospatial Indexing System in PostgreSQL [![test-linux](https://github.com/zachasme/h3-pg/workflows/test-linux/badge.svg)](https://github.com/zachasme/h3-pg/actions) [![test-macos](https://github.com/zachasme/h3-pg/workflows/test-macos/badge.svg)](https://github.com/zachasme/h3-pg/actions/workflows/test-macos.yml) [![test-windows](https://github.com/zachasme/h3-pg/workflows/test-windows/badge.svg)](https://github.com/zachasme/h3-pg/actions/workflows/test-windows.yml) [![License](https://img.shields.io/badge/License-Apache2-blue.svg)](LICENSE) This library provides PostgreSQL bindings for the [H3 Core Library](https://github.com/uber/h3). For API reference, please see the [H3 Documentation](https://uber.github.io/h3). Developed in collaboration with [Scandinavian Highlands](http://www.scandinavian-highlands.com). ## Binary distributions These don't require you have the development headers or cmake installed. ### Debian/Ubuntu (Ubuntu 22.04 LTS (Jammy Jellyfish) +) Replace 16 with your postgresql version. Refer to [PGDG Ubuntu](https://www.postgresql.org/download/linux/ubuntu/) and [PGDG Debian](https://www.postgresql.org/download/linux/debian/) for installing PostgreSQL. More details about the various packages at https://apt.postgresql.org ``` sudo apt install postgresql-16-h3 ``` ### Redhat Derivatives (Rocky / EL 8+ / Fedora 37+) Replace 16 with your postgresql version Refer to [PGDG Redhat Derivatives](https://www.postgresql.org/download/linux/redhat/) for installing PostgreSQL. More details about the various packages at https://yum.postgresql.org ``` sudo yum install h3-pg_16 ``` ### Windows Included as part of PostGIS Bundle 3.3+ for PostgreSQL 11-16 Details: [postgis.net windows](https://postgis.net/documentation/getting_started/install_windows/released_versions/) Works with [PostgreSQL Windows](https://www.postgresql.org/download/windows/) and PostGIS bundle is accessible via the included Application Stackbuilder. ## Compiling Prerequisites - PostgreSQL 11+ (_including server headers_, e.g. `postgresql-server-dev-14`) - C compiler (e.g., `gcc`) - [CMake](https://cmake.org/) 3.20+ - GNU Make ## Quick Overview If the prerequisites are met you can use the [PGXN Client](docs/pgxnclient.md) to download, build, and install, e.g.: ```shell $ pgxn install h3 $ pgxn load h3 $ psql =# SELECT h3_lat_lng_to_cell(POINT('37.3615593,-122.0553238'), 5); h3_lat_lng_to_cell ----------------- 85e35e73fffffff (1 row) ``` (You can install a specific version using `pgxn install 'h3=3.7.2'` and `pgxn load 'h3=3.7.2'` for example) See [Building](#building) for other installation methods. ## Usage > :tada: **Note:** The following usage docs apply to **H3 v4**, which was released on August 23, 2022. > > - For v3 docs, [see the latest v3.x.x release](https://github.com/zachasme/h3-pg/blob/v3.7.2/README.md). > - For breaking changes in v4, [see the CHANGELOG](./CHANGELOG.md). In particular, most [function names have changed](https://h3geo.org/docs/library/migration-3.x/functions). Generally, all functions have been renamed from camelCase in H3 to snake\_case in SQL. See [API reference](https://pgxn.org/dist/h3/docs/api.html) for all provided functions. ## Building ```bash # Generate native build system cmake -B build -DCMAKE_BUILD_TYPE=Release # Build extension(s) cmake --build build # Install extensions (might require sudo) cmake --install build --component h3-pg ``` ## Contributing Pull requests and GitHub issues are welcome. Please include tests for new work. Please note that the purpose of this extension is to expose the API of the H3 Core library, so we will rarely accept new features that are not part of that API. New proposed feature work is more appropriate in the core C library or in a new extension that depends on h3-pg. See [Development](docs/development.md). ## License This project is released under the [Apache 2.0 License](LICENSE.md). h3-pg-4.2.2/cmake/000077500000000000000000000000001475234715600135335ustar00rootroot00000000000000h3-pg-4.2.2/cmake/AddPostgreSQLExtension.cmake000066400000000000000000000063041475234715600210510ustar00rootroot00000000000000# Add pg_regress binary find_program(PostgreSQL_REGRESS pg_regress HINTS "${PostgreSQL_PKG_LIBRARY_DIR}/pgxs/src/test/regress/" "${PostgreSQL_BIN_DIR}" ) # Add pg_validate_extupgrade binary find_program(PostgreSQL_VALIDATE_EXTUPGRADE pg_validate_extupgrade) # Add pgindent binary find_program(PostgreSQL_INDENT pgindent) # Helper command to add extensions function(PostgreSQL_add_extension LIBRARY_NAME) set(options RELOCATABLE) set(oneValueArgs NAME COMMENT VERSION COMPONENT) set(multiValueArgs REQUIRES SOURCES INSTALLS UPDATES) cmake_parse_arguments(EXTENSION "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # Default extension name to same as library name if(NOT EXTENSION_NAME) set(EXTENSION_NAME ${LIBRARY_NAME}) endif() # Allow extensions without sources if(EXTENSION_SOURCES) # Add extension as a dynamically linked library add_library(${LIBRARY_NAME} MODULE ${EXTENSION_SOURCES}) # Link extension to PostgreSQL target_link_libraries(${LIBRARY_NAME} PRIVATE PostgreSQL::PostgreSQL) # Handle macOS specifics if(APPLE) # Fix apple missing symbols set_target_properties(${LIBRARY_NAME} PROPERTIES LINK_FLAGS ${PostgreSQL_LINK_FLAGS}) # Since Postgres 16, the shared library extension on macOS is `dylib`, not `so`. # Ref https://github.com/postgres/postgres/commit/b55f62abb2c2e07dfae99e19a2b3d7ca9e58dc1a if (${PostgreSQL_VERSION_MAJOR} VERSION_GREATER_EQUAL "16") set_target_properties (${LIBRARY_NAME} PROPERTIES SUFFIX ".dylib") endif() endif() # Final touches on output file set_target_properties(${LIBRARY_NAME} PROPERTIES OUTPUT_NAME ${EXTENSION_NAME} INTERPROCEDURAL_OPTIMIZATION TRUE #C_VISIBILITY_PRESET hidden # @TODO: how to get this working? PREFIX "" # Avoid lib* prefix on output file ) # Install .so/.dll to pkglib-dir install( TARGETS ${LIBRARY_NAME} LIBRARY DESTINATION "${PostgreSQL_PKG_LIBRARY_DIR}" COMPONENT ${EXTENSION_COMPONENT} ) endif() # Generate .control file string(REPLACE ";" ", " EXTENSION_REQUIRES "${EXTENSION_REQUIRES}") configure_file( ${CMAKE_SOURCE_DIR}/cmake/control.in ${EXTENSION_NAME}.control ) # Generate .sql install file set(EXTENSION_INSTALL ${CMAKE_CURRENT_BINARY_DIR}/${EXTENSION_NAME}--${EXTENSION_VERSION}.sql) file(WRITE "${EXTENSION_INSTALL}.in" "") foreach(file ${EXTENSION_INSTALLS}) file(READ ${file} CONTENTS) file(APPEND "${EXTENSION_INSTALL}.in" "${CONTENTS}") endforeach() configure_file("${EXTENSION_INSTALL}.in" "${EXTENSION_INSTALL}" COPYONLY) # Install everything else into share-dir install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${EXTENSION_NAME}.control ${EXTENSION_INSTALL} ${EXTENSION_UPDATES} DESTINATION "${PostgreSQL_SHARE_DIR}/extension" COMPONENT ${EXTENSION_COMPONENT} ) # Setup auto-format if(PostgreSQL_INDENT) add_custom_target("format_${EXTENSION_NAME}" COMMAND ${PostgreSQL_INDENT} ${EXTENSION_SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Formatting ${EXTENSION_NAME} sources" ) add_dependencies(${LIBRARY_NAME} "format_${EXTENSION_NAME}") endif() endfunction() h3-pg-4.2.2/cmake/FindPostgreSQL.cmake000066400000000000000000000202271475234715600173440ustar00rootroot00000000000000# Based on: # https://cmake.org/cmake/help/latest/manual/cmake-developer.7.html#find-modules # ---------------------------------------------------------------------------- # https://www.postgresql.org/support/versioning/ set(PostgreSQL_SUPPORTED_VERSIONS ${PostgreSQL_ADDITIONAL_VERSIONS} "17" "16" "15" "14" "13") # Use `FIND_VERSION` to locate specific version of pg_config, or fallback to known versions if(PostgreSQL_FIND_VERSION) list(PREPEND PostgreSQL_SUPPORTED_VERSIONS "${PostgreSQL_FIND_VERSION_MAJOR}") endif() foreach(suffix ${PostgreSQL_SUPPORTED_VERSIONS}) if(WIN32) list(APPEND PostgreSQL_CONFIG_PATH_SUFFIXES "PostgreSQL/${suffix}/bin") endif() if(UNIX) list(APPEND PostgreSQL_CONFIG_HINTS "/usr/lib/postgresql/${suffix}/bin") endif() endforeach() # Configuration will be based on values gathered from `pg_config` find_program(PostgreSQL_CONFIG pg_config REQUIRED HINTS ${PostgreSQL_CONFIG_HINTS} PATH_SUFFIXES ${PostgreSQL_CONFIG_PATH_SUFFIXES} ) # Grab information about the installed version of PostgreSQL execute_process(COMMAND ${PostgreSQL_CONFIG} --version OUTPUT_VARIABLE PostgreSQL_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${PostgreSQL_CONFIG} --bindir OUTPUT_VARIABLE PostgreSQL_BIN_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${PostgreSQL_CONFIG} --sharedir OUTPUT_VARIABLE PostgreSQL_SHARE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${PostgreSQL_CONFIG} --includedir OUTPUT_VARIABLE PostgreSQL_INCLUDE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${PostgreSQL_CONFIG} --pkgincludedir OUTPUT_VARIABLE PostgreSQL_PKG_INCLUDE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${PostgreSQL_CONFIG} --includedir-server OUTPUT_VARIABLE PostgreSQL_SERVER_INCLUDE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${PostgreSQL_CONFIG} --libdir OUTPUT_VARIABLE PostgreSQL_LIBRARY_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${PostgreSQL_CONFIG} --pkglibdir OUTPUT_VARIABLE PostgreSQL_PKG_LIBRARY_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) # @TODO: Figure out if we need _INCLUDE_DIR and/or _PKG_INCLUDE_DIR # Create include dirs list list(APPEND PostgreSQL_INCLUDE_DIRS "${PostgreSQL_INCLUDE_DIR}" "${PostgreSQL_PKG_INCLUDE_DIR}" "${PostgreSQL_SERVER_INCLUDE_DIR}") list(APPEND PostgreSQL_LIBRARY_DIRS "${PostgreSQL_LIBRARY_DIR}") # Set library to search for (which is different on WIN32) set(FIND_LIBRARY pq) # Platform fixes: Windows # https://wiki.postgresql.org/wiki/Building_and_Installing_PostgreSQL_Extension_Modules if(WIN32) set(FIND_LIBRARY postgres) list(APPEND PostgreSQL_INCLUDE_DIRS "${PostgreSQL_SERVER_INCLUDE_DIR}/port/win32") if(MSVC) list(APPEND PostgreSQL_INCLUDE_DIRS "${PostgreSQL_SERVER_INCLUDE_DIR}/port/win32_msvc") endif(MSVC) endif(WIN32) # Platform fixes: MacOS # https://github.com/postgres/postgres/blob/master/src/makefiles/Makefile.darwin set(PostgreSQL_LINK_FLAGS "") # @TODO: Find out if this can be done in a more cmakey way if(APPLE) find_program(PostgreSQL_EXECUTABLE postgres REQUIRED NO_DEFAULT_PATH PATHS ${PostgreSQL_BIN_DIR}) set(PostgreSQL_LINK_FLAGS "-bundle_loader ${PostgreSQL_EXECUTABLE}") endif() # ---------------------------------------------------------------------------- # Now we need to find the libraries and include files # if the library is available with multiple configurations, you can use SelectLibraryConfigurations # to automatically set the _LIBRARY variable: # @TODO: Find out if we can actually find any debug libraries on each platform find_library(PostgreSQL_LIBRARY_RELEASE NAMES ${FIND_LIBRARY} PATHS ${PostgreSQL_LIBRARY_DIRS} ) find_library(PostgreSQL_LIBRARY_DEBUG NAMES ${FIND_LIBRARY}d # <-- debug PATHS ${PostgreSQL_LIBRARY_DIRS} ) include(SelectLibraryConfigurations) select_library_configurations(PostgreSQL) # ---------------------------------------------------------------------------- # If you have a good way of getting the version (from a header file, for example), # you can use that information to set Foo_VERSION (although note that find modules have # traditionally used Foo_VERSION_STRING, so you may want to set both). string(REGEX MATCHALL "[0-9]+" PostgreSQL_VERSION_LIST "${PostgreSQL_VERSION}") list(GET PostgreSQL_VERSION_LIST 0 PostgreSQL_VERSION_MAJOR) list(GET PostgreSQL_VERSION_LIST 1 PostgreSQL_VERSION_MINOR) set(PostgreSQL_VERSION "${PostgreSQL_VERSION_MAJOR}.${PostgreSQL_VERSION_MINOR}") # ---------------------------------------------------------------------------- # Handle additional components if("PostGIS" IN_LIST PostgreSQL_FIND_COMPONENTS) find_library( POSTGIS NAMES postgis postgis-3 postgis-3.so PATHS ${PostgreSQL_PKG_LIBRARY_DIR} ) set(PostgreSQL_PostGIS_FOUND TRUE) if(NOT POSTGIS) set(PostgreSQL_PostGIS_FOUND FALSE) endif() endif() # ---------------------------------------------------------------------------- # Now we can use FindPackageHandleStandardArgs to do # most of the rest of the work for us include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PostgreSQL REQUIRED_VARS PostgreSQL_LIBRARY PostgreSQL_INCLUDE_DIRS HANDLE_COMPONENTS VERSION_VAR PostgreSQL_VERSION ) # ---------------------------------------------------------------------------- # At this point, we have to provide a way for users of the find module to link to the library or libraries that were found. # # If the library is available with multiple configurations, the IMPORTED_CONFIGURATIONS target property should also be populated: if(PostgreSQL_FOUND) # When providing imported targets, these should be namespaced (hence the Foo:: prefix); # CMake will recognize that values passed to target_link_libraries() that contain :: in their name # are supposed to be imported targets (rather than just library names), if (NOT TARGET PostgreSQL::PostgreSQL) add_library(PostgreSQL::PostgreSQL UNKNOWN IMPORTED) endif() # The RELEASE variant should be listed first in the property so that the variant is chosen # if the user uses a configuration which is not an exact match for any listed IMPORTED_CONFIGURATIONS. if (PostgreSQL_LIBRARY_RELEASE) set_property(TARGET PostgreSQL::PostgreSQL APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE ) set_target_properties(PostgreSQL::PostgreSQL PROPERTIES IMPORTED_LOCATION_RELEASE "${PostgreSQL_LIBRARY_RELEASE}" ) endif() if (PostgreSQL_LIBRARY_DEBUG) set_property(TARGET PostgreSQL::PostgreSQL APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG ) set_target_properties(PostgreSQL::PostgreSQL PROPERTIES IMPORTED_LOCATION_DEBUG "${PostgreSQL_LIBRARY_DEBUG}" ) endif() set_target_properties(PostgreSQL::PostgreSQL PROPERTIES #INTERFACE_COMPILE_OPTIONS "${PostgreSQL_CFLAGS_OTHER}" INTERFACE_INCLUDE_DIRECTORIES "${PostgreSQL_INCLUDE_DIRS}" ) endif() # Most of the cache variables should be hidden in the `ccmake` interface unless the user explicitly asks to edit them. mark_as_advanced( PostgreSQL_INCLUDE_DIRS PostgreSQL_LIBRARY ) # ---------------------------------------------------------------------------- # Traditional variables (from pkg-config): # # _FOUND has the package been found # _LIBRARIES ? # _LINK_LIBRARIES ? # _LIBRARY_DIRS ? # _LDFLAGS ? # _LDFLAGS_OTHER ? # _INCLUDE_DIRS ? # _CFLAGS ? # _CFLAGS_OTHER required non-include-dir CFLAGS to stdout # # _VERSION full version of found package # _PREFIX ? # _INCLUDEDIR ? # _LIBDIR ? # debug message(STATUS "Find version: ${PostgreSQL_FIND_VERSION}") message(STATUS "Find major: ${PostgreSQL_FIND_VERSION_MAJOR}") message(STATUS "Find minor: ${PostgreSQL_FIND_VERSION_MINOR}") message(STATUS "Found version: ${PostgreSQL_VERSION}") message(STATUS "Found major: ${PostgreSQL_VERSION_MAJOR}") message(STATUS "Found minor: ${PostgreSQL_VERSION_MINOR}") # Get all variables GET_CMAKE_PROPERTY(vars VARIABLES) FOREACH(var ${vars}) STRING(REGEX MATCH "^PostgreSQL_" item ${var}) IF(item) message(STATUS "${var} = ${${var}}") ENDIF(item) ENDFOREACH(var) h3-pg-4.2.2/cmake/control.in000066400000000000000000000002201475234715600155350ustar00rootroot00000000000000comment = '@EXTENSION_COMMENT@' default_version = '@EXTENSION_VERSION@' relocatable = @EXTENSION_RELOCATABLE@ requires = '@EXTENSION_REQUIRES@' h3-pg-4.2.2/cmake/h3/000077500000000000000000000000001475234715600140455ustar00rootroot00000000000000h3-pg-4.2.2/cmake/h3/CMakeLists.txt000066400000000000000000000011741475234715600166100ustar00rootroot00000000000000include(FetchContent) # Skip core tooling we won't need for extension set(ENABLE_COVERAGE OFF) set(BUILD_BENCHMARKS OFF) set(BUILD_FUZZERS OFF) set(BUILD_FILTERS OFF) set(BUILD_GENERATORS OFF) set(ENABLE_FORMAT OFF) set(ENABLE_LINTING OFF) set(ENABLE_DOCS OFF) FetchContent_Declare( h3 URL https://github.com/uber/h3/archive/refs/tags/v${H3_CORE_VERSION}.tar.gz URL_HASH SHA256=${H3_CORE_SHA256} ) FetchContent_MakeAvailable(h3) set_target_properties(h3 PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE C_VISIBILITY_PRESET hidden ) set(H3_INCLUDE_DIR ${h3_BINARY_DIR}/src/h3lib/include) h3-pg-4.2.2/docs/000077500000000000000000000000001475234715600134035ustar00rootroot00000000000000h3-pg-4.2.2/docs/api.md000066400000000000000000000562351475234715600145110ustar00rootroot00000000000000# API Reference The `h3` extension wraps the [H3 Core Library](https://github.com/uber/h3). The detailed API reference is in the core [H3 Documentation](https://uber.github.io/h3) under the API Reference section. The `h3` core functions have been renamed from camelCase in H3 core to snake\_case in SQL. The SQL function name is prefixed with `h3_`. # Base type An unsigned 64-bit integer representing any H3 object (hexagon, pentagon, directed edge ...) represented as a (or 16-character) hexadecimal string, like '8928308280fffff'. # Indexing functions These function are used for finding the H3 index containing coordinates, and for finding the center and boundary of H3 indexes. ### h3_lat_lng_to_cell(latlng `point`, resolution `integer`) ⇒ `h3index` *Since v4.0.0* See also: h3_lat_lng_to_cell(`geometry`, `integer`), h3_lat_lng_to_cell(`geography`, `integer`) Indexes the location at the specified resolution. ### h3_cell_to_lat_lng(cell `h3index`) ⇒ `point` *Since v4.0.0* See also: h3_cell_to_geometry(`h3index`), h3_cell_to_geography(`h3index`) Finds the centroid of the index. ### h3_cell_to_boundary(cell `h3index`) ⇒ `polygon` *Since v4.0.0* See also: h3_cell_to_boundary_geometry(`h3index`), h3_cell_to_boundary_geography(`h3index`) Finds the boundary of the index. Use `SET h3.extend_antimeridian TO true` to extend coordinates when crossing 180th meridian. # Index inspection functions These functions provide metadata about an H3 index, such as its resolution or base cell, and provide utilities for converting into and out of the 64-bit representation of an H3 index. ### h3_get_resolution(`h3index`) ⇒ `integer` *Since v1.0.0* Returns the resolution of the index. ### h3_get_base_cell_number(`h3index`) ⇒ `integer` *Since v4.0.0* Returns the base cell number of the index. ### h3_is_valid_cell(`h3index`) ⇒ `boolean` *Since v1.0.0* Returns true if the given H3Index is valid. ### h3_is_res_class_iii(`h3index`) ⇒ `boolean` *Since v1.0.0* Returns true if this index has a resolution with Class III orientation. ### h3_is_pentagon(`h3index`) ⇒ `boolean` *Since v1.0.0* Returns true if this index represents a pentagonal cell. ### h3_get_icosahedron_faces(`h3index`) ⇒ `integer[]` *Since v4.0.0* Find all icosahedron faces intersected by a given H3 index. # Grid traversal functions Grid traversal allows finding cells in the vicinity of an origin cell, and determining how to traverse the grid from one cell to another. ### h3_grid_disk(origin `h3index`, [k `integer` = 1]) ⇒ SETOF `h3index` *Since v4.0.0* Produces indices within "k" distance of the origin index. ### h3_grid_disk_distances(origin `h3index`, [k `integer` = 1], OUT index `h3index`, OUT distance `int`) ⇒ SETOF `record` *Since v4.0.0* Produces indices within "k" distance of the origin index paired with their distance to the origin. ### h3_grid_ring_unsafe(origin `h3index`, [k `integer` = 1]) ⇒ SETOF `h3index` *Since v4.0.0* Returns the hollow hexagonal ring centered at origin with distance "k". ### h3_grid_path_cells(origin `h3index`, destination `h3index`) ⇒ SETOF `h3index` *Since v4.0.0* See also: h3_grid_path_cells_recursive(`h3index`, `h3index`) 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. ### h3_grid_distance(origin `h3index`, destination `h3index`) ⇒ `bigint` *Since v4.0.0* Returns the distance in grid cells between the two indices. ### h3_cell_to_local_ij(origin `h3index`, index `h3index`) ⇒ `point` *Since v0.2.0* Produces local IJ coordinates for an H3 index anchored by an origin. ### h3_local_ij_to_cell(origin `h3index`, coord `point`) ⇒ `h3index` *Since v0.2.0* Produces an H3 index from local IJ coordinates anchored by an origin. # Hierarchical grid functions These functions permit moving between resolutions in the H3 grid system. The functions produce parent (coarser) or children (finer) cells. ### h3_cell_to_parent(cell `h3index`, resolution `integer`) ⇒ `h3index` *Since v4.0.0* Returns the parent of the given index. ### h3_cell_to_children(cell `h3index`, resolution `integer`) ⇒ SETOF `h3index` *Since v4.0.0* Returns the set of children of the given index. ### h3_cell_to_center_child(cell `h3index`, resolution `integer`) ⇒ `h3index` *Since v4.0.0* Returns the center child (finer) index contained by input index at given resolution. ### h3_compact_cells(cells `h3index[]`) ⇒ SETOF `h3index` *Since v4.0.0* Compacts the given array as best as possible. ### h3_cell_to_child_pos(child `h3index`, parentRes `integer`) ⇒ `int8` *Since v4.1.0* Returns the position of the child cell within an ordered list of all children of the cells parent at the specified resolution parentRes. The order of the ordered list is the same as that returned by cellToChildren. This is the complement of childPosToCell. ### h3_child_pos_to_cell(childPos `int8`, parent `h3index`, childRes `int`) ⇒ `h3index` *Since v4.1.0* Returns the child cell at a given position within an ordered list of all children of parent at the specified resolution childRes. The order of the ordered list is the same as that returned by cellToChildren. This is the complement of cellToChildPos. ### h3_uncompact_cells(cells `h3index[]`, resolution `integer`) ⇒ SETOF `h3index` *Since v4.0.0* Uncompacts the given array at the given resolution. ### h3_cell_to_parent(cell `h3index`) ⇒ `h3index` *Since v4.0.0* Returns the parent of the given index. ### h3_cell_to_children(cell `h3index`) ⇒ SETOF `h3index` *Since v4.0.0* Returns the set of children of the given index. ### h3_cell_to_center_child(cell `h3index`) ⇒ `h3index` *Since v4.0.0* Returns the center child (finer) index contained by input index at next resolution. ### h3_uncompact_cells(cells `h3index[]`) ⇒ SETOF `h3index` *Since v4.0.0* Uncompacts the given array at the resolution one higher than the highest resolution in the set. ### h3_cell_to_children_slow(index `h3index`, resolution `integer`) ⇒ SETOF `h3index` *Since v4.0.0* Slower version of H3ToChildren but allocates less memory. ### h3_cell_to_children_slow(index `h3index`) ⇒ SETOF `h3index` Slower version of H3ToChildren but allocates less memory. # Region functions These functions convert H3 indexes to and from polygonal areas. ### h3_polygon_to_cells(exterior `polygon`, holes `polygon[]`, [resolution `integer` = 1]) ⇒ SETOF `h3index` *Since v4.0.0* See also: h3_polygon_to_cells(`geometry`, `integer`), h3_polygon_to_cells(`geography`, `integer`) Takes an exterior polygon [and a set of hole polygon] and returns the set of hexagons that best fit the structure. ### h3_polygon_to_cells_experimental(exterior `polygon`, holes `polygon[]`, [resolution `integer` = 1], [containment_mode `text` = center]) ⇒ SETOF `h3index` *Since v4.2.0* Takes an exterior polygon [and a set of hole polygon] and returns the set of hexagons that best fit the structure. ### h3_cells_to_multi_polygon(`h3index[]`, OUT exterior `polygon`, OUT holes `polygon[]`) ⇒ SETOF `record` *Since v4.0.0* See also: h3_cells_to_multi_polygon_geometry(`h3index[]`), h3_cells_to_multi_polygon_geography(`h3index[]`), h3_cells_to_multi_polygon_geometry(setof `h3index`), h3_cells_to_multi_polygon_geography(setof `h3index`) 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. # Unidirectional edge functions Unidirectional edges allow encoding the directed edge from one cell to a neighboring cell. ### h3_are_neighbor_cells(origin `h3index`, destination `h3index`) ⇒ `boolean` *Since v4.0.0* Returns true if the given indices are neighbors. ### h3_cells_to_directed_edge(origin `h3index`, destination `h3index`) ⇒ `h3index` *Since v4.0.0* Returns a unidirectional edge H3 index based on the provided origin and destination. ### h3_is_valid_directed_edge(edge `h3index`) ⇒ `boolean` *Since v4.0.0* Returns true if the given edge is valid. ### h3_get_directed_edge_origin(edge `h3index`) ⇒ `h3index` *Since v4.0.0* Returns the origin index from the given edge. ### h3_get_directed_edge_destination(edge `h3index`) ⇒ `h3index` *Since v4.0.0* Returns the destination index from the given edge. ### h3_directed_edge_to_cells(edge `h3index`, OUT origin `h3index`, OUT destination `h3index`) ⇒ `record` *Since v4.0.0* Returns the pair of indices from the given edge. ### h3_origin_to_directed_edges(`h3index`) ⇒ SETOF `h3index` *Since v4.0.0* Returns all unidirectional edges with the given index as origin. ### h3_directed_edge_to_boundary(edge `h3index`) ⇒ `polygon` *Since v4.0.0* Provides the coordinates defining the unidirectional edge. # H3 Vertex functions Functions for working with cell vertexes. ### h3_cell_to_vertex(cell `h3index`, vertexNum `integer`) ⇒ `h3index` *Since v4.0.0* Returns a single vertex for a given cell, as an H3 index. ### h3_cell_to_vertexes(cell `h3index`) ⇒ SETOF `h3index` *Since v4.0.0* Returns all vertexes for a given cell, as H3 indexes. ### h3_vertex_to_lat_lng(vertex `h3index`) ⇒ `point` *Since v4.0.0* Get the geocoordinates of an H3 vertex. ### h3_is_valid_vertex(vertex `h3index`) ⇒ `boolean` *Since v4.0.0* Whether the input is a valid H3 vertex. # Miscellaneous H3 functions These functions include descriptions of the H3 grid system. ### h3_great_circle_distance(a `point`, b `point`, [unit `text` = km]) ⇒ `double precision` *Since v4.0.0* The great circle distance in radians between two spherical coordinates. ### h3_get_hexagon_area_avg(resolution `integer`, [unit `text` = km]) ⇒ `double precision` *Since v4.0.0* Average hexagon area in square (kilo)meters at the given resolution. ### h3_cell_area(cell `h3index`, [unit `text` = km^2]) ⇒ `double precision` *Since v4.0.0* Exact area for a specific cell (hexagon or pentagon). ### h3_get_hexagon_edge_length_avg(resolution `integer`, [unit `text` = km]) ⇒ `double precision` *Since v4.0.0* Average hexagon edge length in (kilo)meters at the given resolution. ### h3_edge_length(edge `h3index`, [unit `text` = km]) ⇒ `double precision` *Since v4.0.0* Exact length for a specific unidirectional edge. ### h3_get_num_cells(resolution `integer`) ⇒ `bigint` *Since v4.0.0* Number of unique H3 indexes at the given resolution. ### h3_get_res_0_cells() ⇒ SETOF `h3index` *Since v4.0.0* Returns all 122 resolution 0 indexes. ### h3_get_pentagons(resolution `integer`) ⇒ SETOF `h3index` *Since v4.0.0* All the pentagon H3 indexes at the specified resolution. # Operators ### Operator: `h3index` <-> `h3index` *Since v3.7.0* Returns the distance in grid cells between the two indices (at the lowest resolution of the two). ## B-tree operators ### Operator: `h3index` = `h3index` *Since v0.1.0* Returns true if two indexes are the same. ### Operator: `h3index` <> `h3index` *Since v0.1.0* ## R-tree Operators ### Operator: `h3index` && `h3index` *Since v3.6.1* Returns true if the two H3 indexes intersect. ### Operator: `h3index` @> `h3index` *Since v3.6.1* Returns true if A contains B. ### Operator: `h3index` <@ `h3index` *Since v3.6.1* Returns true if A is contained by B. ## SP-GiST operator class (experimental) *This is still an experimental feature and may change in future versions.* Add an SP-GiST index using the `h3index_ops_experimental` operator class: ```sql -- CREATE INDEX [indexname] ON [tablename] USING spgist([column] h3index_ops_experimental); CREATE INDEX spgist_idx ON h3_data USING spgist(hex h3index_ops_experimental); ``` # Type casts ### `h3index` :: `bigint` Convert H3 index to bigint, which is useful when you need a decimal representation. ### `bigint` :: `h3index` Convert bigint to H3 index. ### `h3index` :: `point` Convert H3 index to point. # Extension specific functions ### h3_get_extension_version() ⇒ `text` *Since v1.0.0* Get the currently installed version of the extension. ### h3_pg_migrate_pass_by_reference(`h3index`) ⇒ `h3index` *Since v4.1.0* Migrate h3index from pass-by-reference to pass-by-value. # Deprecated functions ### h3_cell_to_boundary(cell `h3index`, extend_antimeridian `boolean`) ⇒ `polygon` DEPRECATED: Use `SET h3.extend_antimeridian TO true` instead. # PostGIS Integration The `GEOMETRY` data passed to `h3-pg` PostGIS functions should be in SRID 4326. This is an expectation of the core H3 library. Using other SRIDs, such as 3857, can result in either errors or invalid data depending on the function. For example, the `h3_polygon_to_cells()` function will fail with an error in this scenario while the `h3_lat_lng_to_cell()` function will return an invalid geometry. # PostGIS Indexing Functions ### h3_lat_lng_to_cell(`geometry`, resolution `integer`) ⇒ `h3index` *Since v4.0.0* Indexes the location at the specified resolution. ### h3_lat_lng_to_cell(`geography`, resolution `integer`) ⇒ `h3index` *Since v4.0.0* Indexes the location at the specified resolution. ### h3_cell_to_geometry(`h3index`) ⇒ `geometry` *Since v4.0.0* Finds the centroid of the index. ### h3_cell_to_geography(`h3index`) ⇒ `geography` *Since v4.0.0* Finds the centroid of the index. ### h3_cell_to_boundary_geometry(`h3index`) ⇒ `geometry` *Since v4.0.0* Finds the boundary of the index. Splits polygons when crossing 180th meridian. ### h3_cell_to_boundary_geography(`h3index`) ⇒ `geography` *Since v4.0.0* Finds the boundary of the index. Splits polygons when crossing 180th meridian. # PostGIS Grid Traversal Functions ### h3_grid_path_cells_recursive(origin `h3index`, destination `h3index`) ⇒ SETOF `h3index` *Since v4.1.0* # PostGIS Region Functions ### h3_polygon_to_cells(multi `geometry`, resolution `integer`) ⇒ SETOF `h3index` *Since v4.0.0* ### h3_polygon_to_cells(multi `geography`, resolution `integer`) ⇒ SETOF `h3index` *Since v4.0.0* ### h3_cells_to_multi_polygon_geometry(`h3index[]`) ⇒ `geometry` *Since v4.1.0* ### h3_cells_to_multi_polygon_geography(`h3index[]`) ⇒ `geography` *Since v4.1.0* ### h3_cells_to_multi_polygon_geometry(setof `h3index`) *Since v4.1.0* ### h3_cells_to_multi_polygon_geography(setof `h3index`) *Since v4.1.0* ### h3_polygon_to_cells_experimental(multi `geometry`, resolution `integer`, [containment_mode `text` = center]) ⇒ SETOF `h3index` *Since v4.2.0* ### h3_polygon_to_cells_experimental(multi `geography`, resolution `integer`, [containment_mode `text` = center]) ⇒ SETOF `h3index` *Since v4.2.0* # PostGIS Operators ### Operator: `geometry` @ `integer` *Since v4.1.3* Index geometry at specified resolution. ### Operator: `geography` @ `integer` *Since v4.1.3* Index geography at specified resolution. # PostGIS casts ### `h3index` :: `geometry` *Since v0.3.0* ### `h3index` :: `geography` *Since v0.3.0* # WKB indexing functions ### h3_cell_to_boundary_wkb(cell `h3index`) ⇒ `bytea` *Since v4.1.0* Finds the boundary of the index, converts to EWKB. Splits polygons when crossing 180th meridian. This function has to return WKB since Postgres does not provide multipolygon type. # WKB regions functions ### h3_cells_to_multi_polygon_wkb(`h3index[]`) ⇒ `bytea` *Since v4.1.0* Create a LinkedGeoPolygon describing the outline(s) of a set of hexagons, converts to EWKB. Splits polygons when crossing 180th meridian. # Raster processing functions ## Continuous raster data For rasters with pixel values representing continuous data (temperature, humidity, elevation), the data inside H3 cells can be summarized by calculating number of pixels, sum, mean, standard deviation, min and max for each cell inside a raster and grouping these stats across multiple rasters by H3 index. ``` SELECT (summary).h3 AS h3, (h3_raster_summary_stats_agg((summary).stats)).* FROM ( SELECT h3_raster_summary(rast, 8) AS summary FROM rasters ) t GROUP BY 1; h3 | count | sum | mean | stddev | min | max -----------------+-------+--------------------+---------------------+--------------------+-------+------------------ 882d638189fffff | 10 | 4.607657432556152 | 0.46076574325561526 | 1.3822972297668457 | 0 | 4.607657432556152 882d64c4d1fffff | 10 | 3.6940908953547478 | 0.3694090895354748 | 1.099336879464068 | 0 | 3.667332887649536 882d607431fffff | 11 | 6.219290263950825 | 0.5653900239955295 | 1.7624673707119065 | 0 | 6.13831996917724 <...> ``` *Since v4.1.1* ### h3_raster_summary_stats_agg(setof `h3_raster_summary_stats`) *Since v4.1.1* ### h3_raster_summary_clip(rast `raster`, resolution `integer`, [nband `integer` = 1]) ⇒ TABLE (h3 `h3index`, stats `h3_raster_summary_stats`) *Since v4.1.1* Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Clips the raster by H3 cell geometries and processes each part separately. ### h3_raster_summary_centroids(rast `raster`, resolution `integer`, [nband `integer` = 1]) ⇒ TABLE (h3 `h3index`, stats `h3_raster_summary_stats`) *Since v4.1.1* Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Finds corresponding H3 cell for each pixel, then groups values by H3 index. ### h3_raster_summary_subpixel(rast `raster`, resolution `integer`, [nband `integer` = 1]) ⇒ TABLE (h3 `h3index`, stats `h3_raster_summary_stats`) *Since v4.1.1* Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Assumes H3 cell is smaller than a pixel. Finds corresponding pixel for each H3 cell in raster. ### h3_raster_summary(rast `raster`, resolution `integer`, [nband `integer` = 1]) ⇒ TABLE (h3 `h3index`, stats `h3_raster_summary_stats`) *Since v4.1.1* Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Attempts to select an appropriate method based on number of pixels per H3 cell. ## Discrete raster data For rasters where pixels have discrete values corresponding to different classes of land cover or land use, H3 cell data summary can be represented by a JSON object with separate fields for each class. First, value, number of pixels and approximate area are calculated for each H3 cell and value in a raster, then the stats are grouped across multiple rasters by H3 index and value, and after that stats for different values in a cell are combined into a single JSON object. The following example query additionally calculates a fraction of H3 cell pixels for each value, using a window function to get a total number of pixels: ``` WITH summary AS ( -- get aggregated summary for each H3 index/value pair SELECT h3, val, h3_raster_class_summary_item_agg(summary) AS item FROM rasters, h3_raster_class_summary(rast, 8) GROUP BY 1, 2), summary_total AS ( -- add total number of pixels per H3 cell SELECT h3, val, item, sum((item).count) OVER (PARTITION BY h3) AS total FROM summary) SELECT h3, jsonb_object_agg( concat('class_', val::text), h3_raster_class_summary_item_to_jsonb(item) -- val, count, area || jsonb_build_object('fraction', (item).count / total) -- add fraction value ORDER BY val ) AS summary FROM summary_total GROUP BY 1; h3 | summary ----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------- 88194e6f3bfffff | {"class_1": {"area": 75855.5748, "count": 46, "value": 1, "fraction": 0.4509}, "class_2": {"area": 92345.9171, "count": 56, "value": 2, "fraction": 0.5490}} 88194e6f37fffff | {"class_1": {"area": 255600.3064, "count": 155, "value": 1, "fraction": 0.5}, "class_2": {"area": 255600.3064, "count": 155, "value": 2, "fraction": 0.5}} 88194e6f33fffff | {"class_1": {"area": 336402.9840, "count": 204, "value": 1, "fraction": 0.5125}, "class_2": {"area": 319912.6416, "count": 194, "value": 2, "fraction": 0.4874}} <...> ``` Area covered by pixels with the most frequent value in each cell: ``` SELECT DISTINCT ON (h3) h3, val, (item).area FROM ( SELECT h3, val, h3_raster_class_summary_item_agg(summary) AS item FROM rasters, h3_raster_class_summary(rast, 8) GROUP BY 1, 2 ) t ORDER BY h3, (item).count DESC; h3 | val | area -----------------+-----+-------------------- 88194e6f3bfffff | 5 | 23238.699360251427 88194e6f37fffff | 9 | 60863.26022922993 88194e6f33fffff | 8 | 76355.72646939754 <...> ``` *Since v4.1.1* ### h3_raster_class_summary_item_to_jsonb(item `h3_raster_class_summary_item`) ⇒ `jsonb` *Since v4.1.1* Convert raster summary to JSONB, example: `{"count": 10, "value": 2, "area": 16490.3423}` ### h3_raster_class_summary_item_agg(setof `h3_raster_class_summary_item`) *Since v4.1.1* ### h3_raster_class_summary_clip(rast `raster`, resolution `integer`, [nband `integer` = 1]) ⇒ TABLE (h3 `h3index`, val `integer`, summary `h3_raster_class_summary_item`) *Since v4.1.1* Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Clips the raster by H3 cell geometries and processes each part separately. ### h3_raster_class_summary_centroids(rast `raster`, resolution `integer`, [nband `integer` = 1]) ⇒ TABLE (h3 `h3index`, val `integer`, summary `h3_raster_class_summary_item`) *Since v4.1.1* Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Finds corresponding H3 cell for each pixel, then groups by H3 and value. ### h3_raster_class_summary_subpixel(rast `raster`, resolution `integer`, [nband `integer` = 1]) ⇒ TABLE (h3 `h3index`, val `integer`, summary `h3_raster_class_summary_item`) *Since v4.1.1* Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Assumes H3 cell is smaller than a pixel. Finds corresponding pixel for each H3 cell in raster. ### h3_raster_class_summary(rast `raster`, resolution `integer`, [nband `integer` = 1]) ⇒ TABLE (h3 `h3index`, val `integer`, summary `h3_raster_class_summary_item`) *Since v4.1.1* Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Attempts to select an appropriate method based on number of pixels per H3 cell. h3-pg-4.2.2/docs/development.md000066400000000000000000000022641475234715600162530ustar00rootroot00000000000000## Development In order to build and test your changes, simply run `./scripts/develop`. Documentation is generated from the sql files, using the script `scripts/documentaion` (requires poetry). ## Release Process 1. Update version number - Don't follow semver, simply use major and minor from H3 core and increment patch. - Version number should be changed in root `CMakeLists.txt`. - Set `INSTALL_VERSION` to "${PROJECT_VERSION}". - Update files (and cmake references) suffixed `--unreleased` should be renamed. - Installer `.sql` files should have `@ availability` comments updated. - Update changelog by moving from `Unreleased` to a new section - Push and merge changes in `release-x.y.z` branch. 2. Create a release on GitHub - Draft new release "vX.Y.Z" - Copy CHANGELOG.md entry into release description 3. Distribute the extension on PGXN - Run `scripts/bundle` to package the release - Upload the distribution on [PGXN Manager](https://manager.pgxn.org/) 4. Prepare for development - Set `INSTALL_VERSION` to `unreleased` in root `CMakeLists.txt`. - Create new update files with `--unreleased` suffix. - Add them to relevant `CMakeLists.txt` files. h3-pg-4.2.2/docs/pgxnclient.md000066400000000000000000000010411475234715600160740ustar00rootroot00000000000000# PGXN Client > `pgxnclient` does not normally ship with PostgreSQL, so you will probably have to install it manually. The [PGXN Client](https://pgxn.github.io/pgxnclient/) is a command line tool designed to interact with the [PostgreSQL Extension Network](https://pgxn.org/) allowing searching, compiling, installing, and removing extensions in PostgreSQL databases. ## Ubuntu On Ubuntu you can install using `apt` ```bash apt-get install pgxnclient ``` ## MacOS On MacOs you can install using `brew` ```bash brew install pgxnclient ```h3-pg-4.2.2/h3/000077500000000000000000000000001475234715600127655ustar00rootroot00000000000000h3-pg-4.2.2/h3/CMakeLists.txt000066400000000000000000000054121475234715600155270ustar00rootroot00000000000000PostgreSQL_add_extension(postgresql_h3 RELOCATABLE NAME h3 COMMENT "H3 bindings for PostgreSQL" VERSION ${INSTALL_VERSION} COMPONENT ${PROJECT_NAME} SOURCES src/binding/edge.c src/binding/hierarchy.c src/binding/indexing.c src/binding/inspection.c src/binding/miscellaneous.c src/binding/regions.c src/binding/traversal.c src/binding/vertex.c src/deprecated.c src/extension.c src/guc.c src/init.c src/opclass_btree.c src/opclass_hash.c src/opclass_spgist.c src/operators.c src/srf.c src/type.c INSTALLS sql/install/00-type.sql sql/install/01-indexing.sql sql/install/02-inspection.sql sql/install/03-traversal.sql sql/install/04-hierarchy.sql sql/install/05-regions.sql sql/install/06-edge.sql sql/install/07-vertex.sql sql/install/08-miscellaneous.sql sql/install/10-operators.sql sql/install/11-opclass_btree.sql sql/install/12-opclass_hash.sql sql/install/13-opclass_brin.sql sql/install/14-opclass_spgist.sql sql/install/20-casts.sql sql/install/30-extension.sql sql/install/99-deprecated.sql UPDATES sql/updates/h3--0.1.0.sql sql/updates/h3--0.1.0--0.2.0.sql sql/updates/h3--0.2.0--0.3.0.sql sql/updates/h3--0.3.0--0.3.1.sql sql/updates/h3--0.3.1--0.3.2.sql sql/updates/h3--0.3.2--0.4.0.sql sql/updates/h3--0.4.0--1.0.0.sql sql/updates/h3--1.0.0--1.0.1.sql sql/updates/h3--1.0.1--1.0.2.sql sql/updates/h3--1.0.2--1.0.3.sql sql/updates/h3--1.0.3--1.0.4.sql sql/updates/h3--1.0.4--1.0.5.sql sql/updates/h3--1.0.5--1.0.6.sql sql/updates/h3--1.0.6--3.4.0.sql sql/updates/h3--3.4.0--3.4.1.sql sql/updates/h3--3.4.1--3.5.0.sql sql/updates/h3--3.5.0--3.6.0.sql sql/updates/h3--3.6.0--3.6.1.sql sql/updates/h3--3.6.1--3.6.2.sql sql/updates/h3--3.6.2--3.6.3.sql sql/updates/h3--3.6.3--3.6.4.sql sql/updates/h3--3.6.4--3.6.5.sql sql/updates/h3--3.6.5--3.7.0.sql sql/updates/h3--3.7.0--3.7.1.sql sql/updates/h3--3.7.1--3.7.2.sql sql/updates/h3--3.7.2--4.0.0.sql sql/updates/h3--4.0.0--4.0.1.sql sql/updates/h3--4.0.1--4.0.2.sql sql/updates/h3--4.0.2--4.0.3.sql sql/updates/h3--4.0.3--4.1.0.sql sql/updates/h3--4.1.0--4.1.1.sql sql/updates/h3--4.1.1--4.1.2.sql sql/updates/h3--4.1.2--4.1.3.sql sql/updates/h3--4.1.3--4.1.4.sql sql/updates/h3--4.1.4--4.2.0.sql sql/updates/h3--4.2.0--4.2.1.sql sql/updates/h3--4.2.1--4.2.2.sql ) # configure configure_file(src/config.h.in src/config.h) # include target_include_directories(postgresql_h3 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src src ) # link target_link_libraries(postgresql_h3 PRIVATE postgresql_h3_shared h3 ) # test if(BUILD_TESTING) add_subdirectory(test) endif() h3-pg-4.2.2/h3/sql/000077500000000000000000000000001475234715600135645ustar00rootroot00000000000000h3-pg-4.2.2/h3/sql/install/000077500000000000000000000000001475234715600152325ustar00rootroot00000000000000h3-pg-4.2.2/h3/sql/install/00-type.sql000066400000000000000000000044241475234715600171550ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION h3" to load this file. \quit -- ---------- ---------- ---------- ---------- ---------- ---------- ---------- --| The `h3` extension wraps the [H3 Core Library](https://github.com/uber/h3). --| The detailed API reference is in the core [H3 Documentation](https://uber.github.io/h3) under the API Reference section. --| --| The `h3` core functions have been renamed from camelCase in H3 core to snake\_case in SQL. --| The SQL function name is prefixed with `h3_`. --| --| # Base type --| --| An unsigned 64-bit integer representing any H3 object (hexagon, pentagon, directed edge ...) --| represented as a (or 16-character) hexadecimal string, like '8928308280fffff'. -- ---------- ---------- ---------- ---------- ---------- ---------- ---------- -- declare shell type, allowing us to reference while defining functions -- before finally providing the full definition of the data type CREATE TYPE h3index; --@ internal CREATE OR REPLACE FUNCTION h3index_in(cstring) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_out(h3index) RETURNS cstring AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_recv(internal) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_send(h3index) RETURNS bytea AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE TYPE h3index ( INPUT = h3index_in, OUTPUT = h3index_out, RECEIVE = h3index_recv, SEND = h3index_send, LIKE = int8 ); h3-pg-4.2.2/h3/sql/install/01-indexing.sql000066400000000000000000000034511475234715600200010ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Indexing functions --| --| These function are used for finding the H3 index containing coordinates, --| and for finding the center and boundary of H3 indexes. --@ availability: 4.0.0 --@ ref: h3_lat_lng_to_cell_geometry, h3_lat_lng_to_cell_geography CREATE OR REPLACE FUNCTION h3_lat_lng_to_cell(latlng point, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_lat_lng_to_cell(point, integer) IS 'Indexes the location at the specified resolution.'; --@ availability: 4.0.0 --@ ref: h3_cell_to_geometry, h3_cell_to_geography CREATE OR REPLACE FUNCTION h3_cell_to_lat_lng(cell h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_lat_lng(h3index) IS 'Finds the centroid of the index.'; --@ availability: 4.0.0 --@ ref: h3_cell_to_boundary_geometry, h3_cell_to_boundary_geography CREATE OR REPLACE FUNCTION h3_cell_to_boundary(cell h3index) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary(h3index) IS 'Finds the boundary of the index. Use `SET h3.extend_antimeridian TO true` to extend coordinates when crossing 180th meridian.'; h3-pg-4.2.2/h3/sql/install/02-inspection.sql000066400000000000000000000045601475234715600203520ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Index inspection functions --| --| These functions provide metadata about an H3 index, such as its resolution --| or base cell, and provide utilities for converting into and out of the --| 64-bit representation of an H3 index. --@ availability: 1.0.0 CREATE OR REPLACE FUNCTION h3_get_resolution(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_resolution(h3index) IS 'Returns the resolution of the index.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_base_cell_number(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_base_cell_number(h3index) IS 'Returns the base cell number of the index.'; --@ availability: 1.0.0 CREATE OR REPLACE FUNCTION h3_is_valid_cell(h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_valid_cell(h3index) IS 'Returns true if the given H3Index is valid.'; --@ availability: 1.0.0 CREATE OR REPLACE FUNCTION h3_is_res_class_iii(h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_res_class_iii(h3index) IS 'Returns true if this index has a resolution with Class III orientation.'; --@ availability: 1.0.0 CREATE OR REPLACE FUNCTION h3_is_pentagon(h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_pentagon(h3index) IS 'Returns true if this index represents a pentagonal cell.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_icosahedron_faces(h3index) RETURNS integer[] AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_icosahedron_faces(h3index) IS 'Find all icosahedron faces intersected by a given H3 index.'; h3-pg-4.2.2/h3/sql/install/03-traversal.sql000066400000000000000000000063141475234715600202020ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Grid traversal functions --| --| Grid traversal allows finding cells in the vicinity of an origin cell, and --| determining how to traverse the grid from one cell to another. --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_grid_disk(origin h3index, k integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_disk(h3index, integer) IS 'Produces indices within "k" distance of the origin index.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_grid_disk_distances(origin h3index, k integer DEFAULT 1, OUT index h3index, OUT distance int) RETURNS SETOF record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_disk_distances(h3index, integer) IS 'Produces indices within "k" distance of the origin index paired with their distance to the origin.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_grid_ring_unsafe(origin h3index, k integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_ring_unsafe(h3index, integer) IS 'Returns the hollow hexagonal ring centered at origin with distance "k".'; --@ availability: 4.0.0 --@ ref: h3_grid_path_cells_recursive CREATE OR REPLACE FUNCTION h3_grid_path_cells(origin h3index, destination h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_path_cells(h3index, h3index) IS '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.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_grid_distance(origin h3index, destination h3index) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_distance(h3index, h3index) IS 'Returns the distance in grid cells between the two indices.'; --@ availability: 0.2.0 CREATE OR REPLACE FUNCTION h3_cell_to_local_ij(origin h3index, index h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_local_ij(h3index, h3index) IS 'Produces local IJ coordinates for an H3 index anchored by an origin.'; --@ availability: 0.2.0 CREATE OR REPLACE FUNCTION h3_local_ij_to_cell(origin h3index, coord point) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_local_ij_to_cell(h3index, point) IS 'Produces an H3 index from local IJ coordinates anchored by an origin.'; h3-pg-4.2.2/h3/sql/install/04-hierarchy.sql000066400000000000000000000137351475234715600201630ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Hierarchical grid functions --| --| These functions permit moving between resolutions in the H3 grid system. --| The functions produce parent (coarser) or children (finer) cells. --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_parent(cell h3index, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_parent(cell h3index, resolution integer) IS 'Returns the parent of the given index.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_children(cell h3index, resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_children(cell h3index, resolution integer) IS 'Returns the set of children of the given index.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_center_child(cell h3index, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_center_child(cell h3index, resolution integer) IS 'Returns the center child (finer) index contained by input index at given resolution.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_compact_cells(cells h3index[]) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_compact_cells(cells h3index[]) IS 'Compacts the given array as best as possible.'; --@ availability: 4.1.0 CREATE OR REPLACE FUNCTION h3_cell_to_child_pos(child h3index, parentRes integer) RETURNS int8 AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_child_pos(child h3index, parentRes integer) IS 'Returns the position of the child cell within an ordered list of all children of the cells parent at the specified resolution parentRes. The order of the ordered list is the same as that returned by cellToChildren. This is the complement of childPosToCell.'; --@ availability: 4.1.0 CREATE OR REPLACE FUNCTION h3_child_pos_to_cell(childPos int8, parent h3index, childRes int) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_child_pos_to_cell(childPos int8, parent h3index, childRes int) IS 'Returns the child cell at a given position within an ordered list of all children of parent at the specified resolution childRes. The order of the ordered list is the same as that returned by cellToChildren. This is the complement of cellToChildPos.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_uncompact_cells(cells h3index[], resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_uncompact_cells(cells h3index[], resolution integer) IS 'Uncompacts the given array at the given resolution.'; -- ---------- ---------- ---------- ---------- ---------- ---------- ---------- -- Custom Funtions --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_parent(cell h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_parent(cell h3index) IS 'Returns the parent of the given index.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_children(cell h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_children(cell h3index) IS 'Returns the set of children of the given index.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_center_child(cell h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_center_child(cell h3index) IS 'Returns the center child (finer) index contained by input index at next resolution.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_uncompact_cells(cells h3index[]) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_uncompact_cells(cells h3index[]) IS 'Uncompacts the given array at the resolution one higher than the highest resolution in the set.'; --@ internal CREATE OR REPLACE FUNCTION __h3_cell_to_children_aux(index h3index, resolution integer, current integer) RETURNS SETOF h3index AS $$ DECLARE retSet h3index[]; r h3index; BEGIN IF current = -1 THEN SELECT h3_get_resolution(index) into current; END IF; IF resolution = -1 THEN SELECT h3_get_resolution(index)+1 into resolution; END IF; IF current < resolution THEN SELECT ARRAY(SELECT h3_cell_to_children(index)) into retSet; FOREACH r in ARRAY retSet LOOP RETURN QUERY SELECT __h3_cell_to_children_aux(r, resolution, current + 1); END LOOP; ELSE RETURN NEXT index; END IF; END;$$ LANGUAGE plpgsql; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_children_slow(index h3index, resolution integer) RETURNS SETOF h3index AS $$ SELECT __h3_cell_to_children_aux($1, $2, -1) $$ LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_children_slow(index h3index, resolution integer) IS 'Slower version of H3ToChildren but allocates less memory.'; CREATE OR REPLACE FUNCTION h3_cell_to_children_slow(index h3index) RETURNS SETOF h3index AS $$ SELECT __h3_cell_to_children_aux($1, -1, -1) $$ LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_children_slow(index h3index) IS 'Slower version of H3ToChildren but allocates less memory.'; h3-pg-4.2.2/h3/sql/install/05-regions.sql000066400000000000000000000045341475234715600176510ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Region functions --| --| These functions convert H3 indexes to and from polygonal areas. --@ availability: 4.0.0 --@ ref: h3_polygon_to_cells_geometry, h3_polygon_to_cells_geography CREATE OR REPLACE FUNCTION h3_polygon_to_cells(exterior polygon, holes polygon[], resolution integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE -- intentionally NOT STRICT CALLED ON NULL INPUT PARALLEL SAFE; COMMENT ON FUNCTION h3_polygon_to_cells(polygon, polygon[], integer) IS 'Takes an exterior polygon [and a set of hole polygon] and returns the set of hexagons that best fit the structure.'; --@ availability: 4.2.0 CREATE OR REPLACE FUNCTION h3_polygon_to_cells_experimental(exterior polygon, holes polygon[], resolution integer DEFAULT 1, containment_mode text DEFAULT 'center') RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE -- intentionally NOT STRICT CALLED ON NULL INPUT PARALLEL SAFE; COMMENT ON FUNCTION h3_polygon_to_cells_experimental(polygon, polygon[], integer, text) IS 'Takes an exterior polygon [and a set of hole polygon] and returns the set of hexagons that best fit the structure.'; --@ availability: 4.0.0 --@ ref: h3_cells_to_multi_polygon_geometry, h3_cells_to_multi_polygon_geography, h3_cells_to_multi_polygon_geometry_agg, h3_cells_to_multi_polygon_geography_agg CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon(h3index[], OUT exterior polygon, OUT holes polygon[]) RETURNS SETOF record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cells_to_multi_polygon(h3index[]) IS '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.'; h3-pg-4.2.2/h3/sql/install/06-edge.sql000066400000000000000000000062021475234715600171020ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Unidirectional edge functions --| --| Unidirectional edges allow encoding the directed edge from one cell to a --| neighboring cell. --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_are_neighbor_cells(origin h3index, destination h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_are_neighbor_cells(origin h3index, destination h3index) IS 'Returns true if the given indices are neighbors.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cells_to_directed_edge(origin h3index, destination h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cells_to_directed_edge(origin h3index, destination h3index) IS 'Returns a unidirectional edge H3 index based on the provided origin and destination.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_is_valid_directed_edge(edge h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_valid_directed_edge(edge h3index) IS 'Returns true if the given edge is valid.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_directed_edge_origin(edge h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_directed_edge_origin(edge h3index) IS 'Returns the origin index from the given edge.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_directed_edge_destination(edge h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_directed_edge_destination(edge h3index) IS 'Returns the destination index from the given edge.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_directed_edge_to_cells(edge h3index, OUT origin h3index, OUT destination h3index) RETURNS record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_directed_edge_to_cells(edge h3index) IS 'Returns the pair of indices from the given edge.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_origin_to_directed_edges(h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_origin_to_directed_edges(h3index) IS 'Returns all unidirectional edges with the given index as origin.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_directed_edge_to_boundary(edge h3index) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_directed_edge_to_boundary(edge h3index) IS 'Provides the coordinates defining the unidirectional edge.'; h3-pg-4.2.2/h3/sql/install/07-vertex.sql000066400000000000000000000034101475234715600175120ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ --| # H3 Vertex functions --| --| Functions for working with cell vertexes. --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_vertex(cell h3index, vertexNum integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_vertex(cell h3index, vertexNum integer) IS 'Returns a single vertex for a given cell, as an H3 index.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_to_vertexes(cell h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_vertexes(cell h3index) IS 'Returns all vertexes for a given cell, as H3 indexes.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_vertex_to_lat_lng(vertex h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_vertex_to_lat_lng(vertex h3index) IS 'Get the geocoordinates of an H3 vertex.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_is_valid_vertex(vertex h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_valid_vertex(vertex h3index) IS 'Whether the input is a valid H3 vertex.'; h3-pg-4.2.2/h3/sql/install/08-miscellaneous.sql000066400000000000000000000060661475234715600210530ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Miscellaneous H3 functions --| --| These functions include descriptions of the H3 grid system. --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_great_circle_distance(a point, b point, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_great_circle_distance(point, point, text) IS 'The great circle distance in radians between two spherical coordinates.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_hexagon_area_avg(resolution integer, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_hexagon_area_avg(integer, text) IS 'Average hexagon area in square (kilo)meters at the given resolution.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_cell_area(cell h3index, unit text DEFAULT 'km^2') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_area(h3index, text) IS 'Exact area for a specific cell (hexagon or pentagon).'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_hexagon_edge_length_avg(resolution integer, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_hexagon_edge_length_avg(integer, text) IS 'Average hexagon edge length in (kilo)meters at the given resolution.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_edge_length(edge h3index, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_edge_length(h3index, text) IS 'Exact length for a specific unidirectional edge.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_num_cells(resolution integer) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_num_cells(integer) IS 'Number of unique H3 indexes at the given resolution.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_res_0_cells() RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_res_0_cells() IS 'Returns all 122 resolution 0 indexes.'; --@ availability: 4.0.0 CREATE OR REPLACE FUNCTION h3_get_pentagons(resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_pentagons(resolution integer) IS 'All the pentagon H3 indexes at the specified resolution.';h3-pg-4.2.2/h3/sql/install/10-operators.sql000066400000000000000000000110431475234715600202060ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Operators --@ internal CREATE OR REPLACE FUNCTION h3index_distance(h3index, h3index) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ availability: 3.7.0 CREATE OPERATOR <-> ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_distance, COMMUTATOR = <-> ); COMMENT ON OPERATOR <-> (h3index, h3index) IS 'Returns the distance in grid cells between the two indices (at the lowest resolution of the two).'; -- ---------- ---------- ---------- ---------- ---------- ---------- ---------- --| ## B-tree operators --@ internal CREATE OR REPLACE FUNCTION h3index_eq(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ availability: 0.1.0 CREATE OPERATOR = ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_eq, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES ); COMMENT ON OPERATOR = (h3index, h3index) IS 'Returns true if two indexes are the same.'; --@ internal CREATE OR REPLACE FUNCTION h3index_ne(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ availability: 0.1.0 CREATE OPERATOR <> ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_ne, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); --@ internal CREATE OR REPLACE FUNCTION h3index_lt(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OPERATOR < ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_lt, COMMUTATOR = > , NEGATOR = >= , RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); --@ internal CREATE OR REPLACE FUNCTION h3index_le(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OPERATOR <= ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_le, COMMUTATOR = >= , NEGATOR = > , RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); --@ internal CREATE OR REPLACE FUNCTION h3index_gt(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OPERATOR > ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_gt, COMMUTATOR = < , NEGATOR = <= , RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); --@ internal CREATE OR REPLACE FUNCTION h3index_ge(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OPERATOR >= ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_ge, COMMUTATOR = <= , NEGATOR = < , RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); -- ---------- ---------- ---------- ---------- ---------- ---------- ---------- --| ## R-tree Operators --@ internal CREATE OR REPLACE FUNCTION h3index_overlaps(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ availability: 3.6.1 CREATE OPERATOR && ( PROCEDURE = h3index_overlaps, LEFTARG = h3index, RIGHTARG = h3index, COMMUTATOR = &&, RESTRICT = contsel, JOIN = contjoinsel ); COMMENT ON OPERATOR && (h3index, h3index) IS 'Returns true if the two H3 indexes intersect.'; --@ internal CREATE OR REPLACE FUNCTION h3index_contains(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ availability: 3.6.1 CREATE OPERATOR @> ( PROCEDURE = h3index_contains, LEFTARG = h3index, RIGHTARG = h3index, COMMUTATOR = <@, RESTRICT = contsel, JOIN = contjoinsel ); COMMENT ON OPERATOR @> (h3index, h3index) IS 'Returns true if A contains B.'; --@ internal CREATE OR REPLACE FUNCTION h3index_contained_by(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ availability: 3.6.1 CREATE OPERATOR <@ ( PROCEDURE = h3index_contained_by, LEFTARG = h3index, RIGHTARG = h3index, COMMUTATOR = @>, RESTRICT = contsel, JOIN = contjoinsel ); COMMENT ON OPERATOR <@ (h3index, h3index) IS 'Returns true if A is contained by B.'; h3-pg-4.2.2/h3/sql/install/11-opclass_btree.sql000066400000000000000000000023101475234715600210130ustar00rootroot00000000000000/* * Copyright 2019 Zacharias Knudsen * * 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. */ -- B-tree operator class --@ internal CREATE OR REPLACE FUNCTION h3index_cmp(h3index, h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_sortsupport(internal) RETURNS void AS 'h3', 'h3index_sortsupport' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OPERATOR CLASS h3index_ops DEFAULT FOR TYPE h3index USING btree AS OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 h3index_cmp(h3index, h3index), FUNCTION 2 h3index_sortsupport(internal); h3-pg-4.2.2/h3/sql/install/12-opclass_hash.sql000066400000000000000000000021241475234715600206410ustar00rootroot00000000000000/* * Copyright 2019 Zacharias Knudsen * * 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. */ -- Hash operator class --@ internal CREATE OR REPLACE FUNCTION h3index_hash(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_hash_extended(h3index, int8) RETURNS int8 AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OPERATOR CLASS h3index_ops DEFAULT FOR TYPE h3index USING hash AS OPERATOR 1 = , FUNCTION 1 h3index_hash(h3index), FUNCTION 2 h3index_hash_extended(h3index, int8); h3-pg-4.2.2/h3/sql/install/13-opclass_brin.sql000066400000000000000000000021061475234715600206510ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- BRIN operator class --@ internal CREATE OPERATOR CLASS h3index_minmax_ops DEFAULT FOR TYPE h3index USING brin AS OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 brin_minmax_opcinfo(internal), FUNCTION 2 brin_minmax_add_value(internal, internal, internal, internal), FUNCTION 3 brin_minmax_consistent(internal, internal, internal), FUNCTION 4 brin_minmax_union(internal, internal, internal); h3-pg-4.2.2/h3/sql/install/14-opclass_spgist.sql000066400000000000000000000055311475234715600212360ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| ## SP-GiST operator class (experimental) --| --| *This is still an experimental feature and may change in future versions.* --| Add an SP-GiST index using the `h3index_ops_experimental` operator class: --| --| ```sql --| -- CREATE INDEX [indexname] ON [tablename] USING spgist([column] h3index_ops_experimental); --| CREATE INDEX spgist_idx ON h3_data USING spgist(hex h3index_ops_experimental); --| ``` --@ internal CREATE OR REPLACE FUNCTION h3index_spgist_config(internal, internal) RETURNS void AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_spgist_choose(internal, internal) RETURNS void AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_spgist_picksplit(internal, internal) RETURNS void AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_spgist_inner_consistent(internal, internal) RETURNS void AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --@ internal CREATE OR REPLACE FUNCTION h3index_spgist_leaf_consistent(internal, internal) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; -- intentionally *not* marked as DEFAULT, -- until we are satisfied with the implementation CREATE OPERATOR CLASS h3index_ops_experimental FOR TYPE h3index USING spgist AS -- OPERATOR 1 << , -- RTLeftStrategyNumber -- OPERATOR 2 &< , -- RTOverLeftStrategyNumber -- OPERATOR 3 && , -- RTOverlapStrategyNumber -- OPERATOR 4 &> , -- RTOverRightStrategyNumber -- OPERATOR 5 >> , -- RTRightStrategyNumber OPERATOR 6 = , -- RTSameStrategyNumber OPERATOR 7 @> , -- RTContainsStrategyNumber OPERATOR 8 <@ , -- RTContainedByStrategyNumber -- OPERATOR 9 &<| , -- RTOverBelowStrategyNumber -- OPERATOR 10 <<| , -- RTBelowStrategyNumber -- OPERATOR 11 |>> , -- RTAboveStrategyNumber -- OPERATOR 12 |&> , -- RTOverAboveStrategyNumber FUNCTION 1 h3index_spgist_config(internal, internal), FUNCTION 2 h3index_spgist_choose(internal, internal), FUNCTION 3 h3index_spgist_picksplit(internal, internal), FUNCTION 4 h3index_spgist_inner_consistent(internal, internal), FUNCTION 5 h3index_spgist_leaf_consistent(internal, internal); h3-pg-4.2.2/h3/sql/install/20-casts.sql000066400000000000000000000025601475234715600173120ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ --| # Type casts --@ internal CREATE OR REPLACE FUNCTION h3index_to_bigint(h3index) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE CAST (h3index AS bigint) WITH FUNCTION h3index_to_bigint(h3index); COMMENT ON CAST (h3index AS bigint) IS 'Convert H3 index to bigint, which is useful when you need a decimal representation.'; --@ internal CREATE OR REPLACE FUNCTION bigint_to_h3index(bigint) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE CAST (bigint AS h3index) WITH FUNCTION bigint_to_h3index(bigint); COMMENT ON CAST (h3index AS bigint) IS 'Convert bigint to H3 index.'; CREATE CAST (h3index AS point) WITH FUNCTION h3_cell_to_lat_lng(h3index); COMMENT ON CAST (h3index AS point) IS 'Convert H3 index to point.';h3-pg-4.2.2/h3/sql/install/30-extension.sql000066400000000000000000000022431475234715600202100ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Extension specific functions --@ availability: 1.0.0 CREATE OR REPLACE FUNCTION h3_get_extension_version() RETURNS text AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_extension_version() IS 'Get the currently installed version of the extension.'; --@ availability: 4.1.0 CREATE OR REPLACE FUNCTION h3_pg_migrate_pass_by_reference(h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_pg_migrate_pass_by_reference(h3index) IS 'Migrate h3index from pass-by-reference to pass-by-value.'; h3-pg-4.2.2/h3/sql/install/99-deprecated.sql000066400000000000000000000016311475234715600203130ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ --| # Deprecated functions CREATE OR REPLACE FUNCTION h3_cell_to_boundary(cell h3index, extend_antimeridian boolean) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary(h3index, boolean) IS 'DEPRECATED: Use `SET h3.extend_antimeridian TO true` instead.'; h3-pg-4.2.2/h3/sql/updates/000077500000000000000000000000001475234715600152315ustar00rootroot00000000000000h3-pg-4.2.2/h3/sql/updates/h3--0.1.0--0.2.0.sql000066400000000000000000000257401475234715600175130ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '0.2.0'" to load this file. \quit -- Indexing functions (indexing.c) CREATE OR REPLACE FUNCTION h3_geo_to_h3(point, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_geo_to_h3(point, resolution integer) IS 'Indexes the location at the specified resolution'; CREATE OR REPLACE FUNCTION h3_h3_to_geo(h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_to_geo(h3index) IS 'Finds the centroid of the index'; CREATE OR REPLACE FUNCTION h3_h3_to_geo_boundary(h3index) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_to_geo_boundary(h3index) IS 'Finds the boundary of the index'; -- Index inspection functions (inspection.c) CREATE OR REPLACE FUNCTION h3_h3_get_resolution(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_get_resolution(h3index) IS 'Returns the resolution of the index'; CREATE OR REPLACE FUNCTION h3_h3_get_base_cell(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_get_base_cell(h3index) IS 'Returns the base cell number of the index'; CREATE OR REPLACE FUNCTION h3_h3_is_valid(h3index) RETURNS bool AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_is_valid(h3index) IS 'Returns true if the given H3Index is valid'; CREATE OR REPLACE FUNCTION h3_h3_is_res_class_iii(h3index) RETURNS bool AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_is_res_class_iii(h3index) IS 'Returns true if this index has a resolution with Class III orientation'; CREATE OR REPLACE FUNCTION h3_h3_is_pentagon(h3index) RETURNS bool AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_is_pentagon(h3index) IS 'Returns true if this index represents a pentagonal cell'; -- Grid traversal functions (traversal.c) CREATE OR REPLACE FUNCTION h3_k_ring(h3index, k integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_k_ring(h3index, k integer) IS 'Produces indices within "k" distance of the origin index'; CREATE OR REPLACE FUNCTION h3_k_ring_distances(h3index, k integer DEFAULT 1, OUT index h3index, OUT distance int) RETURNS SETOF record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_k_ring_distances(h3index, k integer) IS 'Produces indices within "k" distance of the origin index paired with their distance to the origin'; CREATE OR REPLACE FUNCTION h3_hex_ring(h3index, k integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_hex_ring(h3index, k integer) IS 'Returns the hollow hexagonal ring centered at origin with distance "k"'; CREATE OR REPLACE FUNCTION h3_distance(h3index, h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_distance(h3index, h3index) IS 'Returns the distance in grid cells between the two indices'; CREATE OR REPLACE FUNCTION h3_experimental_h3_to_local_ij(origin h3index, index h3index) RETURNS POINT AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_experimental_h3_to_local_ij(origin h3index, index h3index) IS 'Produces local IJ coordinates for an H3 index anchored by an origin. This function is experimental, and its output is not guaranteed to be compatible across different versions of H3.'; CREATE OR REPLACE FUNCTION h3_experimental_local_ij_to_h3(origin h3index, coord POINT) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_experimental_local_ij_to_h3(origin h3index, coord POINT) IS 'Produces an H3 index from local IJ coordinates anchored by an origin. This function is experimental, and its output is not guaranteed to be compatible across different versions of H3.'; -- Hierarchical grid functions (hierarchy.c) CREATE OR REPLACE FUNCTION h3_h3_to_parent(h3index, resolution integer DEFAULT -1) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_to_parent(h3index, resolution integer) IS 'Returns the parent of the given index'; CREATE OR REPLACE FUNCTION h3_h3_to_children(h3index, resolution integer DEFAULT -1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_to_children(index h3index, resolution integer) IS 'Returns the set of children of the given index'; CREATE OR REPLACE FUNCTION h3_compact(h3index[]) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_compact(h3index[]) IS 'Compacts the given array as best as possible'; CREATE OR REPLACE FUNCTION h3_uncompact(h3index[], resolution integer DEFAULT -1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_uncompact(h3index[], resolution integer) IS 'Uncompacts the given array at the given resolution. If no resolution is given, then it is chosen as one higher than the highest resolution in the set'; -- Region functions (regions.c) CREATE OR REPLACE FUNCTION h3_polyfill(exterior polygon, holes polygon[], resolution integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE PARALLEL SAFE; -- NOT STRICT COMMENT ON FUNCTION h3_polyfill(exterior polygon, holes polygon[], resolution integer) IS 'Takes an exterior polygon [and a set of hole polygon] and returns the set of hexagons that best fit the structure'; CREATE OR REPLACE FUNCTION h3_h3_set_to_linked_geo(h3index[], OUT exterior polygon, OUT holes polygon[]) RETURNS SETOF record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_set_to_linked_geo(h3index[]) IS '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'; -- Unidirectional edge functions (uniedges.c) CREATE OR REPLACE FUNCTION h3_h3_indexes_are_neighbors(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_indexes_are_neighbors(h3index, h3index) IS 'Returns true if the given indices are neighbors'; CREATE OR REPLACE FUNCTION h3_get_h3_unidirectional_edge(origin h3index, destination h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_h3_unidirectional_edge(origin h3index, destination h3index) IS 'Returns a unidirectional edge H3 index based on the provided origin and destination.'; CREATE OR REPLACE FUNCTION h3_h3_unidirectional_edge_is_valid(edge h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_unidirectional_edge_is_valid(edge h3index) IS 'Returns true if the given edge is valid.'; CREATE OR REPLACE FUNCTION h3_get_origin_h3_index_from_unidirectional_edge(edge h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_origin_h3_index_from_unidirectional_edge(edge h3index) IS 'Returns the origin index from the given edge.'; CREATE OR REPLACE FUNCTION h3_get_destination_h3_index_from_unidirectional_edge(edge h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_destination_h3_index_from_unidirectional_edge(edge h3index) IS 'Returns the destination index from the given edge.'; CREATE OR REPLACE FUNCTION h3_get_h3_indexes_from_unidirectional_edge(edge h3index, OUT origin h3index, OUT destination h3index) RETURNS record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_h3_indexes_from_unidirectional_edge(edge h3index) IS 'Returns the pair of indices from the given edge.'; CREATE OR REPLACE FUNCTION h3_get_h3_unidirectional_edges_from_hexagon(h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_h3_unidirectional_edges_from_hexagon(h3index) IS 'Returns all unidirectional edges with the given index as origin'; CREATE OR REPLACE FUNCTION h3_get_unidirectional_edge_boundary(edge h3index) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_unidirectional_edge_boundary(edge h3index) IS 'Provides the coordinates defining the unidirectional edge.'; -- Miscellaneous H3 functions (miscellaneous.c) CREATE OR REPLACE FUNCTION h3_num_hexagons(resolution integer) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_num_hexagons(resolution integer) IS 'Number of unique H3 indexes at the given resolution.'; -- DEPRECATED in v3.4.0 CREATE OR REPLACE FUNCTION h3_degs_to_rads(float) RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_rads_to_degs(float) RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; -- DEPRECATED in v3.5.0 CREATE OR REPLACE FUNCTION h3_hex_area_km2(integer) RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_hex_area_m2(integer) RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_edge_length_km(integer) RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_edge_length_m(integer) RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_hex_range(h3index, k integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_hex_range_distances(h3index, k integer, OUT h3index, OUT int) RETURNS SETOF record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_hex_ranges(h3index[], k integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; -- DEPRECATED in v3.6.0 CREATE OR REPLACE FUNCTION h3_string_to_h3(cstring) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_h3_to_string(h3index) RETURNS cstring AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; h3-pg-4.2.2/h3/sql/updates/h3--0.1.0.sql000066400000000000000000000070101475234715600167710ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION h3" to load this file. \quit -- Declare shell type, allowing us to reference while defining functions CREATE TYPE h3index; CREATE OR REPLACE FUNCTION h3index_in(cstring) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_out(h3index) RETURNS cstring AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_eq(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_ne(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_lt(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_le(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_gt(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_ge(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_cmp(h3index, h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; -- Finally, we can provide the full definition of the data type CREATE TYPE h3index ( INTERNALLENGTH = 8, INPUT = h3index_in, OUTPUT = h3index_out, ALIGNMENT = double ); -- Operators CREATE OPERATOR = ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_eq, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES ); CREATE OPERATOR <> ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_ne, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR < ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_lt, COMMUTATOR = > , NEGATOR = >= , RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR <= ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_le, COMMUTATOR = >= , NEGATOR = > , RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR > ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_gt, COMMUTATOR = < , NEGATOR = <= , RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR >= ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_ge, COMMUTATOR = <= , NEGATOR = < , RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); -- Operator class CREATE OPERATOR CLASS btree_h3index_ops DEFAULT FOR TYPE h3index USING btree AS OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 h3index_cmp(h3index, h3index); h3-pg-4.2.2/h3/sql/updates/h3--0.2.0--0.3.0.sql000066400000000000000000000106511475234715600175100ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '0.3.0'" to load this file. \quit -- Custom helper functions CREATE OR REPLACE FUNCTION h3_basecells() RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_basecells() IS 'Returns all 122 basecells.'; CREATE OR REPLACE FUNCTION __h3_h3_to_children_aux(index h3index, resolution integer, current INTEGER) RETURNS SETOF h3index AS $$ DECLARE retSet h3index[]; r h3index; BEGIN IF current = -1 THEN SELECT h3_h3_get_resolution(index) into current; END IF; IF resolution = -1 THEN SELECT h3_h3_get_resolution(index)+1 into resolution; END IF; IF current < resolution THEN SELECT ARRAY(SELECT h3_h3_to_children_fast(index)) into retSet; FOREACH r in ARRAY retSet LOOP RETURN QUERY SELECT __h3_h3_to_children_aux(r, resolution, current + 1); END LOOP; ELSE RETURN NEXT index; END IF; END;$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION h3_h3_to_children_slow(index h3index, resolution integer DEFAULT -1) RETURNS SETOF h3index AS $$ SELECT __h3_h3_to_children_aux($1, $2, -1) $$ LANGUAGE SQL; COMMENT ON FUNCTION h3_h3_to_children_slow(index h3index, resolution integer) IS 'Slower version of H3ToChildren but allocates less memory'; -- PostGIS --CREATE OR REPLACE FUNCTION h3_geo_to_h3(geometry, resolution integer) RETURNS h3index -- AS $$ SELECT h3_geo_to_h3($1::point, $2); $$ LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_geo_to_h3(geography, resolution integer) RETURNS h3index -- AS $$ SELECT h3_geo_to_h3($1::geometry, $2); $$ LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_h3_to_geometry(h3index) RETURNS geometry -- AS $$ SELECT ST_SetSRID(h3_h3_to_geo($1)::geometry, 4326) $$ LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_h3_to_geography(h3index) RETURNS geography -- AS $$ SELECT h3_h3_to_geometry($1)::geography $$ LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_h3_to_geo_boundary_geometry(h3index) RETURNS geometry -- AS $$ SELECT ST_SetSRID(h3_h3_to_geo_boundary($1)::geometry, 4326) $$ LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_h3_to_geo_boundary_geography(h3index) RETURNS geography -- AS $$ SELECT h3_h3_to_geo_boundary_geometry($1)::geography $$ LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_polyfill(multi geometry, resolution integer) RETURNS SETOF h3index -- AS $$ SELECT h3_polyfill(exterior, holes, resolution) FROM ( -- SELECT -- -- extract exterior ring of each polygon -- ST_MakePolygon(ST_ExteriorRing(poly))::polygon exterior, -- -- extract holes of each polygon -- (SELECT array_agg(hole) -- FROM ( -- SELECT ST_MakePolygon(ST_InteriorRingN( -- poly, -- generate_series(1, ST_NumInteriorRings(poly)) -- ))::polygon AS hole -- ) q_hole -- ) holes -- -- extract single polygons from multipolygon -- FROM ( -- select (st_dump(multi)).geom as poly -- ) q_poly GROUP BY poly -- ) h3_polyfill; $$ LANGUAGE SQL IMMUTABLE STRICT; --CREATE OR REPLACE FUNCTION h3_polyfill(multi geography, resolution integer) RETURNS SETOF h3index --AS $$ SELECT h3_polyfill($1::geometry, $2) $$ LANGUAGE SQL; -- Type casts CREATE CAST (h3index AS point) WITH FUNCTION h3_h3_to_geo(h3index); --CREATE CAST (h3index AS geometry) WITH FUNCTION h3_h3_to_geometry(h3index); --CREATE CAST (h3index AS geography) WITH FUNCTION h3_h3_to_geography(h3index); -- DEPRECATED in v1.0.0 CREATE OR REPLACE FUNCTION h3_haversine_distance(h3index, h3index) RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; h3-pg-4.2.2/h3/sql/updates/h3--0.3.0--0.3.1.sql000066400000000000000000000032621475234715600175120ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '0.3.1'" to load this file. \quit DROP FUNCTION IF EXISTS h3_h3_to_geo_boundary(h3index); DROP FUNCTION IF EXISTS h3_h3_to_geo_boundary_geometry(h3index); DROP FUNCTION IF EXISTS h3_h3_to_geo_boundary_geography(h3index); CREATE OR REPLACE FUNCTION h3_h3_to_geo_boundary(h3index, extend_at_meridian BOOLEAN DEFAULT FALSE) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_h3_to_geo_boundary(h3index, boolean) IS 'Finds the boundary of the index, second argument extends coordinates when crossing 180th meridian to help visualization'; --CREATE OR REPLACE FUNCTION h3_h3_to_geo_boundary_geometry(h3index, extend BOOLEAN DEFAULT FALSE) RETURNS geometry -- AS $$ SELECT ST_SetSRID(h3_h3_to_geo_boundary($1, $2)::geometry, 4326) $$ LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_h3_to_geo_boundary_geography(h3index, extend BOOLEAN DEFAULT FALSE) RETURNS geography -- AS $$ SELECT h3_h3_to_geo_boundary_geometry($1, $2)::geography $$ LANGUAGE SQL;h3-pg-4.2.2/h3/sql/updates/h3--0.3.1--0.3.2.sql000066400000000000000000000014241475234715600175120ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '0.3.2'" to load this file. \quit -- NO CHANGES HERE, ONLY IN C FILES h3-pg-4.2.2/h3/sql/updates/h3--0.3.2--0.4.0.sql000066400000000000000000000036731475234715600175220ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '0.4.0'" to load this file. \quit CREATE OR REPLACE FUNCTION h3_line(h3index, h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_line(h3index, h3index) IS '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.'; CREATE OR REPLACE FUNCTION __h3_h3_to_children_aux(index h3index, resolution integer, current INTEGER) RETURNS SETOF h3index AS $$ DECLARE retSet h3index[]; r h3index; BEGIN IF current = -1 THEN SELECT h3_h3_get_resolution(index) into current; END IF; IF resolution = -1 THEN SELECT h3_h3_get_resolution(index)+1 into resolution; END IF; IF current < resolution THEN SELECT ARRAY(SELECT h3_h3_to_children(index)) into retSet; FOREACH r in ARRAY retSet LOOP RETURN QUERY SELECT __h3_h3_to_children_aux(r, resolution, current + 1); END LOOP; ELSE RETURN NEXT index; END IF; END;$$ LANGUAGE plpgsql;h3-pg-4.2.2/h3/sql/updates/h3--0.4.0--1.0.0.sql000066400000000000000000000107221475234715600175070ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION h3" to load this file. \quit DROP FUNCTION IF EXISTS h3_haversine_distance(h3index, h3index); ALTER FUNCTION h3_basecells() RENAME TO h3_get_res_0_indexes; COMMENT ON FUNCTION h3_get_res_0_indexes() IS 'Get all resolution 0 indexes.'; -- rename functions with double (h3_h3_) prefix ALTER FUNCTION h3_h3_get_base_cell(h3index) RENAME TO h3_get_base_cell; ALTER FUNCTION h3_h3_get_resolution(h3index) RENAME TO h3_get_resolution; ALTER FUNCTION h3_h3_is_pentagon(h3index) RENAME TO h3_is_pentagon; ALTER FUNCTION h3_h3_is_res_class_iii(h3index) RENAME TO h3_is_res_class_iii; ALTER FUNCTION h3_h3_is_valid(h3index) RENAME TO h3_is_valid; ALTER FUNCTION h3_h3_indexes_are_neighbors(h3index,h3index) RENAME TO h3_indexes_are_neighbors; ALTER FUNCTION h3_h3_set_to_linked_geo(h3index[]) RENAME TO h3_set_to_linked_geo; ALTER FUNCTION __h3_h3_to_children_aux(h3index,integer,integer) RENAME TO __h3_to_children_aux; ALTER FUNCTION h3_h3_to_children(h3index,integer) RENAME TO h3_to_children; ALTER FUNCTION h3_h3_to_children_slow(h3index,integer) RENAME TO h3_to_children_slow; --ALTER FUNCTION h3_h3_to_geo_boundary_geography(h3index,boolean) RENAME TO h3_to_geo_boundary_geography; --ALTER FUNCTION h3_h3_to_geo_boundary_geometry(h3index,boolean) RENAME TO h3_to_geo_boundary_geometry; ALTER FUNCTION h3_h3_to_geo_boundary(h3index,boolean) RENAME TO h3_to_geo_boundary; --ALTER FUNCTION h3_h3_to_geography(h3index) RENAME TO h3_to_geography; ALTER FUNCTION h3_h3_to_geo(h3index) RENAME TO h3_to_geo; --ALTER FUNCTION h3_h3_to_geometry(h3index) RENAME TO h3_to_geometry; ALTER FUNCTION h3_h3_to_parent(h3index,integer) RENAME TO h3_to_parent; ALTER FUNCTION h3_h3_to_string(h3index) RENAME TO h3_to_string; ALTER FUNCTION h3_h3_unidirectional_edge_is_valid(h3index) RENAME TO h3_unidirectional_edge_is_valid; --CREATE OR REPLACE FUNCTION h3_to_geo_boundary_geometry(h3index, extend BOOLEAN DEFAULT FALSE) RETURNS geometry -- AS $$ SELECT ST_SetSRID(h3_to_geo_boundary($1, $2)::geometry, 4326) $$ LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_to_geo_boundary_geography(h3index, extend BOOLEAN DEFAULT FALSE) RETURNS geography -- AS $$ SELECT h3_to_geo_boundary_geometry($1, $2)::geography $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_to_children_slow(index h3index, resolution integer DEFAULT -1) RETURNS SETOF h3index AS $$ SELECT __h3_to_children_aux($1, $2, -1) $$ LANGUAGE SQL; COMMENT ON FUNCTION h3_to_children_slow(index h3index, resolution integer) IS 'Slower version of H3ToChildren but allocates less memory'; CREATE OR REPLACE FUNCTION __h3_to_children_aux(index h3index, resolution integer, current INTEGER) RETURNS SETOF h3index AS $$ DECLARE retSet h3index[]; r h3index; BEGIN IF current = -1 THEN SELECT h3_get_resolution(index) into current; END IF; IF resolution = -1 THEN SELECT h3_get_resolution(index)+1 into resolution; END IF; IF current < resolution THEN SELECT ARRAY(SELECT h3_to_children(index)) into retSet; FOREACH r in ARRAY retSet LOOP RETURN QUERY SELECT __h3_to_children_aux(r, resolution, current + 1); END LOOP; ELSE RETURN NEXT index; END IF; END;$$ LANGUAGE plpgsql; -- version CREATE OR REPLACE FUNCTION h3_get_extension_version() RETURNS text AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_extension_version() IS 'Get the currently installed version of the extension.'; -- hash operators CREATE OR REPLACE FUNCTION h3index_hash(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OPERATOR CLASS hash_h3index_ops DEFAULT FOR TYPE h3index USING hash AS OPERATOR 1 = , FUNCTION 1 h3index_hash(h3index); h3-pg-4.2.2/h3/sql/updates/h3--1.0.0--1.0.1.sql000066400000000000000000000011501475234715600175000ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- no changes h3-pg-4.2.2/h3/sql/updates/h3--1.0.1--1.0.2.sql000066400000000000000000000011501475234715600175020ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- no changes h3-pg-4.2.2/h3/sql/updates/h3--1.0.2--1.0.3.sql000066400000000000000000000011501475234715600175040ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- no changes h3-pg-4.2.2/h3/sql/updates/h3--1.0.3--1.0.4.sql000066400000000000000000000014241475234715600175120ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '1.0.4'" to load this file. \quit -- NO CHANGES HERE, ONLY IN C FILES h3-pg-4.2.2/h3/sql/updates/h3--1.0.4--1.0.5.sql000066400000000000000000000014241475234715600175140ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '1.0.5'" to load this file. \quit -- NO CHANGES HERE, ONLY IN C FILES h3-pg-4.2.2/h3/sql/updates/h3--1.0.5--1.0.6.sql000066400000000000000000000014241475234715600175160ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '1.0.6'" to load this file. \quit -- NO CHANGES HERE, ONLY IN C FILES h3-pg-4.2.2/h3/sql/updates/h3--1.0.6--3.4.0.sql000066400000000000000000000015201475234715600175140ustar00rootroot00000000000000/* * Copyright 2018 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.4.0'" to load this file. \quit DROP FUNCTION IF EXISTS h3_degs_to_rads(float); DROP FUNCTION IF EXISTS h3_rads_to_degs(float); h3-pg-4.2.2/h3/sql/updates/h3--3.4.0--3.4.1.sql000066400000000000000000000013571475234715600175250ustar00rootroot00000000000000/* * Copyright 2019 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.4.1'" to load this file. \quit h3-pg-4.2.2/h3/sql/updates/h3--3.4.1--3.5.0.sql000066400000000000000000000043111475234715600175170ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.5.0'" to load this file. \quit CREATE OR REPLACE FUNCTION h3_get_faces(h3index) RETURNS integer[] AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_faces(h3index) IS 'Find all icosahedron faces intersected by a given H3 index'; ALTER FUNCTION h3_get_unidirectional_edge_boundary(h3index) RENAME TO h3_get_h3_unidirectional_edge_boundary; -- replace separate length functions with single function DROP FUNCTION IF EXISTS h3_edge_length_km(integer); DROP FUNCTION IF EXISTS h3_edge_length_m(integer); CREATE OR REPLACE FUNCTION h3_edge_length(resolution integer, km boolean DEFAULT FALSE) RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_edge_length(integer, boolean) IS 'Average hexagon edge length in (kilo)meters at the given resolution.'; -- replace separate area functions with single function DROP FUNCTION IF EXISTS h3_hex_area_km2(integer); DROP FUNCTION IF EXISTS h3_hex_area_m2(integer); CREATE OR REPLACE FUNCTION h3_hex_area(resolution integer, km boolean DEFAULT FALSE) RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_hex_area(integer, boolean) IS 'Average hexagon area in square (kilo)meters at the given resolution.'; DROP FUNCTION IF EXISTS h3_hex_range(h3index, integer); DROP FUNCTION IF EXISTS h3_hex_range_distances(h3index, integer); DROP FUNCTION IF EXISTS h3_hex_ranges(h3index[], integer); ALTER FUNCTION h3_set_to_linked_geo(h3index[]) RENAME TO h3_set_to_multi_polygon;h3-pg-4.2.2/h3/sql/updates/h3--3.5.0--3.6.0.sql000066400000000000000000000036721475234715600175310ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.6.0'" to load this file. \quit -- Hierarchical grid functions (hierarchy.c) CREATE OR REPLACE FUNCTION h3_to_center_child(h3index, resolution integer DEFAULT -1) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_to_parent(h3index, resolution integer) IS 'Returns the center child (finer) index contained by input index at given resolution'; -- Miscellaneous H3 functions (miscellaneous.c) CREATE OR REPLACE FUNCTION h3_get_pentagon_indexes(resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_res_0_indexes() IS 'All the pentagon H3 indexes at the specified resolution.'; -- type casts CREATE OR REPLACE FUNCTION h3index_to_bigint(h3index) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION bigint_to_h3index(bigint) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE CAST (h3index AS bigint) WITH FUNCTION h3index_to_bigint(h3index); CREATE CAST (bigint AS h3index) WITH FUNCTION bigint_to_h3index(bigint); -- string conversion already provided by type itself DROP FUNCTION IF EXISTS h3_to_string(h3index); DROP FUNCTION IF EXISTS h3_string_to_h3(cstring);h3-pg-4.2.2/h3/sql/updates/h3--3.6.0--3.6.1.sql000066400000000000000000000032331475234715600175240ustar00rootroot00000000000000/* * Copyright 2019 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.6.1'" to load this file. \quit -- add R-tree operators CREATE OR REPLACE FUNCTION h3index_overlaps(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_contains(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_contained_by(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OPERATOR && ( PROCEDURE = h3index_overlaps, LEFTARG = h3index, RIGHTARG = h3index, COMMUTATOR = &&, RESTRICT = contsel, JOIN = contjoinsel ); CREATE OPERATOR @> ( PROCEDURE = h3index_contains, LEFTARG = h3index, RIGHTARG = h3index, COMMUTATOR = <@, RESTRICT = contsel, JOIN = contjoinsel ); CREATE OPERATOR <@ ( PROCEDURE = h3index_contained_by, LEFTARG = h3index, RIGHTARG = h3index, COMMUTATOR = @>, RESTRICT = contsel, JOIN = contjoinsel ); h3-pg-4.2.2/h3/sql/updates/h3--3.6.1--3.6.2.sql000066400000000000000000000023241475234715600175260ustar00rootroot00000000000000/* * Copyright 2020 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.6.2'" to load this file. \quit -- add sort support (see #24) CREATE OR REPLACE FUNCTION h3index_sortsupport(internal) RETURNS void AS 'h3', 'h3index_sortsupport' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ALTER OPERATOR family btree_h3index_ops USING btree ADD FUNCTION 2 (h3index) h3index_sortsupport(internal); -- pass-by-value on supported systems (see #26) UPDATE pg_type AS sink SET typbyval = source.typbyval FROM ( SELECT typbyval FROM pg_type WHERE typname = 'int8' ) source WHERE typname = 'h3index'; h3-pg-4.2.2/h3/sql/updates/h3--3.6.2--3.6.3.sql000066400000000000000000000013751475234715600175350ustar00rootroot00000000000000/* * Copyright 2020 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.6.3'" to load this file. \quit -- no changesh3-pg-4.2.2/h3/sql/updates/h3--3.6.3--3.6.4.sql000066400000000000000000000013751475234715600175370ustar00rootroot00000000000000/* * Copyright 2020 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.6.4'" to load this file. \quit -- no changesh3-pg-4.2.2/h3/sql/updates/h3--3.6.4--3.6.5.sql000066400000000000000000000036751475234715600175460ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.6.5'" to load this file. \quit -- Fix function flags which were previously only changed in install files ALTER FUNCTION h3_get_h3_unidirectional_edge_boundary(h3index) IMMUTABLE STRICT PARALLEL SAFE; --ALTER FUNCTION h3_geo_to_h3(geometry, integer) -- IMMUTABLE STRICT PARALLEL SAFE; --ALTER FUNCTION h3_geo_to_h3(geography, integer) -- IMMUTABLE STRICT PARALLEL SAFE; --ALTER FUNCTION h3_to_geometry(h3index) -- IMMUTABLE STRICT PARALLEL SAFE; --ALTER FUNCTION h3_to_geography(h3index) -- IMMUTABLE STRICT PARALLEL SAFE; --ALTER FUNCTION h3_to_geo_boundary_geometry(h3index, boolean) -- IMMUTABLE STRICT PARALLEL SAFE; --ALTER FUNCTION h3_to_geo_boundary_geography(h3index, boolean) -- IMMUTABLE STRICT PARALLEL SAFE; --ALTER FUNCTION h3_polyfill(geometry, integer) -- IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT --ALTER FUNCTION h3_polyfill(geography, integer) -- IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT -- Add second support function for hash opclass CREATE OR REPLACE FUNCTION h3index_hash_extended(h3index, int8) RETURNS int8 AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ALTER OPERATOR FAMILY hash_h3index_ops USING hash ADD FUNCTION 2 h3index_hash_extended(h3index, int8);h3-pg-4.2.2/h3/sql/updates/h3--3.6.5--3.7.0.sql000066400000000000000000000066171475234715600175420ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.7.0'" to load this file. \quit CREATE OPERATOR <-> ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3_distance, COMMUTATOR = <-> ); -- Broken since 1.0.0 on update path --CREATE OR REPLACE FUNCTION h3_to_geometry(h3index) RETURNS geometry -- AS $$ SELECT ST_SetSRID(h3_to_geo($1)::geometry, 4326) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; --CREATE OR REPLACE FUNCTION h3_to_geography(h3index) RETURNS geography -- AS $$ SELECT h3_to_geometry($1)::geography $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; -- New functions in core v3.7.0 CREATE OR REPLACE FUNCTION h3_point_dist(a point, b point, unit text DEFAULT 'km') RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_point_dist(point, point, text) IS 'The great circle distance in radians between two spherical coordinates.'; CREATE OR REPLACE FUNCTION h3_cell_area(cell h3index, unit text DEFAULT 'km^2') RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_area(h3index, text) IS 'Exact area for a specific cell (hexagon or pentagon).'; CREATE OR REPLACE FUNCTION h3_exact_edge_length(edge h3index, unit text DEFAULT 'km') RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_exact_edge_length(h3index, text) IS 'Exact length for a specific unidirectional edge.'; -- New call signatures for hexarea and edgelength, using string instead of boolean CREATE OR REPLACE FUNCTION h3_hex_area(resolution integer, unit text DEFAULT 'km') RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_hex_area(integer, text) IS 'Average hexagon area in square (kilo)meters at the given resolution.'; CREATE OR REPLACE FUNCTION h3_edge_length(resolution integer, unit text DEFAULT 'km') RETURNS float AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_edge_length(integer, text) IS 'Average hexagon edge length in (kilo)meters at the given resolution.'; DROP FUNCTION IF EXISTS h3_hex_area(integer, boolean); DROP FUNCTION IF EXISTS h3_edge_length(integer, boolean); CREATE OR REPLACE FUNCTION h3_hex_area(resolution integer, km boolean) RETURNS float AS $$ SELECT h3_hex_area($1, CASE WHEN $2 THEN 'km' ELSE 'm' END) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; COMMENT ON FUNCTION h3_hex_area(integer, boolean) IS 'Deprecated: use string for unit'; CREATE OR REPLACE FUNCTION h3_edge_length(resolution integer, km boolean) RETURNS float AS $$ SELECT h3_edge_length($1, CASE WHEN $2 THEN 'km' ELSE 'm' END) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; COMMENT ON FUNCTION h3_edge_length(integer, boolean) IS 'Deprecated: use string for unit'; h3-pg-4.2.2/h3/sql/updates/h3--3.7.0--3.7.1.sql000066400000000000000000000023201475234715600175220ustar00rootroot00000000000000/* * Copyright 2021 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.7.1'" to load this file. \quit COMMENT ON OPERATOR && (h3index, h3index) IS 'Returns true if the two H3 indexes intersect'; COMMENT ON OPERATOR @> (h3index, h3index) IS 'Returns true if A containts B'; COMMENT ON OPERATOR <@ (h3index, h3index) IS 'Returns true if A is contained by B'; COMMENT ON OPERATOR = (h3index, h3index) IS 'Returns true if two indexes are the same'; COMMENT ON FUNCTION h3_hex_area(integer, boolean) IS NULL; COMMENT ON FUNCTION h3_edge_length(integer, boolean) IS NULL;h3-pg-4.2.2/h3/sql/updates/h3--3.7.1--3.7.2.sql000066400000000000000000000013751475234715600175350ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '3.7.2'" to load this file. \quit -- no changesh3-pg-4.2.2/h3/sql/updates/h3--3.7.2--4.0.0.sql000066400000000000000000000303611475234715600175230ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.0.0'" to load this file. \quit -- Move postgis integration to its own extension DROP FUNCTION IF EXISTS h3_geo_to_h3(geometry, resolution integer); DROP FUNCTION IF EXISTS h3_geo_to_h3(geography, resolution integer); DROP FUNCTION IF EXISTS h3_to_geo_boundary_geometry(h3index); DROP FUNCTION IF EXISTS h3_to_geo_boundary_geography(h3index); DROP FUNCTION IF EXISTS h3_to_geo_boundary_geometry(h3index, extend boolean); DROP FUNCTION IF EXISTS h3_to_geo_boundary_geography(h3index, extend boolean); DROP FUNCTION IF EXISTS h3_polyfill(multi geometry, resolution integer); DROP FUNCTION IF EXISTS h3_polyfill(multi geography, resolution integer); DROP CAST IF EXISTS (h3index AS geometry); DROP CAST IF EXISTS (h3index AS geography); DROP FUNCTION IF EXISTS h3_to_geometry(h3index); DROP FUNCTION IF EXISTS h3_to_geography(h3index); -- H3 Core v4 renames -- indexing ALTER FUNCTION h3_geo_to_h3(point, resolution integer) RENAME TO h3_lat_lng_to_cell; CREATE OR REPLACE FUNCTION h3_lat_lng_to_cell(latlng point, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ALTER FUNCTION h3_to_geo(h3index) RENAME TO h3_cell_to_lat_lng; CREATE OR REPLACE FUNCTION h3_cell_to_lat_lng(cell h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ALTER FUNCTION h3_to_geo_boundary(h3index, extend_at_meridian BOOLEAN) RENAME TO h3_cell_to_boundary; CREATE OR REPLACE FUNCTION h3_cell_to_boundary(cell h3index, extend_at_meridian boolean DEFAULT FALSE) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; -- inspection ALTER FUNCTION h3_get_base_cell(h3index) RENAME TO h3_get_base_cell_number; ALTER FUNCTION h3_is_valid(h3index) RENAME TO h3_is_valid_cell; ALTER FUNCTION h3_get_faces(h3index) RENAME TO h3_get_icosahedron_faces; -- traversal ALTER FUNCTION h3_k_ring(h3index, k integer) RENAME TO h3_grid_disk; CREATE OR REPLACE FUNCTION h3_grid_disk(origin h3index, k integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ALTER FUNCTION h3_k_ring_distances(h3index, k integer, OUT index h3index, OUT distance int) RENAME TO h3_grid_disk_distances; CREATE OR REPLACE FUNCTION h3_grid_disk_distances(origin h3index, k integer DEFAULT 1, OUT index h3index, OUT distance int) RETURNS SETOF record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; DROP FUNCTION h3_hex_ring(h3index, k integer); CREATE OR REPLACE FUNCTION h3_grid_ring_unsafe(origin h3index, k integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_ring_unsafe(h3index, integer) IS 'Returns the hollow hexagonal ring centered at origin with distance "k"'; DROP FUNCTION h3_line(h3index, h3index); CREATE OR REPLACE FUNCTION h3_grid_path_cells(origin h3index, destination h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_path_cells(h3index, h3index) IS '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.'; DROP OPERATOR <-> (h3index, h3index); DROP FUNCTION h3_distance(h3index, h3index); CREATE OR REPLACE FUNCTION h3_grid_distance(origin h3index, destination h3index) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ALTER FUNCTION h3_experimental_h3_to_local_ij(origin h3index, index h3index) RENAME TO h3_cell_to_local_ij; ALTER FUNCTION h3_experimental_local_ij_to_h3(origin h3index, coord POINT) RENAME TO h3_local_ij_to_cell; CREATE OPERATOR <-> ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3_grid_distance, COMMUTATOR = <-> ); COMMENT ON OPERATOR <-> (h3index, h3index) IS 'Returns the distance in grid cells between the two indices'; -- hierarchy DROP FUNCTION h3_to_parent(h3index, resolution integer); CREATE OR REPLACE FUNCTION h3_cell_to_parent(cell h3index, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_parent(cell h3index, resolution integer) IS 'Returns the parent of the given index'; DROP FUNCTION h3_to_children(h3index, resolution integer); CREATE OR REPLACE FUNCTION h3_cell_to_children(cell h3index, resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_children(cell h3index, resolution integer) IS 'Returns the set of children of the given index'; DROP FUNCTION h3_to_center_child(h3index, resolution integer); CREATE OR REPLACE FUNCTION h3_cell_to_center_child(cell h3index, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_center_child(cell h3index, resolution integer) IS 'Returns the center child (finer) index contained by input index at given resolution'; DROP FUNCTION h3_compact(h3index[]); CREATE OR REPLACE FUNCTION h3_compact_cells(cells h3index[]) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_compact_cells(cells h3index[]) IS 'Compacts the given array as best as possible'; DROP FUNCTION h3_uncompact(h3index[], resolution integer); CREATE OR REPLACE FUNCTION h3_uncompact_cells(cells h3index[], resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_uncompact_cells(cells h3index[], resolution integer) IS 'Uncompacts the given array at the given resolution.'; DROP FUNCTION IF EXISTS h3_to_children_slow(index h3index); DROP FUNCTION IF EXISTS h3_to_children_slow(index h3index, resolution integer); DROP FUNCTION IF EXISTS __h3_to_children_aux(index h3index, resolution integer, current integer); --copied new CREATE OR REPLACE FUNCTION h3_cell_to_parent(cell h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_parent(cell h3index) IS 'Returns the parent of the given index'; CREATE OR REPLACE FUNCTION h3_cell_to_children(cell h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_children(cell h3index) IS 'Returns the set of children of the given index'; CREATE OR REPLACE FUNCTION h3_cell_to_center_child(cell h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_center_child(cell h3index) IS 'Returns the center child (finer) index contained by input index at next resolution'; CREATE OR REPLACE FUNCTION h3_uncompact_cells(cells h3index[]) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_uncompact_cells(cells h3index[]) IS 'Uncompacts the given array at the resolution one higher than the highest resolution in the set'; CREATE OR REPLACE FUNCTION __h3_cell_to_children_aux(index h3index, resolution integer, current integer) RETURNS SETOF h3index AS $$ DECLARE retSet h3index[]; r h3index; BEGIN IF current = -1 THEN SELECT h3_get_resolution(index) into current; END IF; IF resolution = -1 THEN SELECT h3_get_resolution(index)+1 into resolution; END IF; IF current < resolution THEN SELECT ARRAY(SELECT h3_cell_to_children(index)) into retSet; FOREACH r in ARRAY retSet LOOP RETURN QUERY SELECT __h3_cell_to_children_aux(r, resolution, current + 1); END LOOP; ELSE RETURN NEXT index; END IF; END;$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION h3_cell_to_children_slow(index h3index, resolution integer) RETURNS SETOF h3index AS $$ SELECT __h3_cell_to_children_aux($1, $2, -1) $$ LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_children_slow(index h3index, resolution integer) IS 'Slower version of H3ToChildren but allocates less memory'; CREATE OR REPLACE FUNCTION h3_cell_to_children_slow(index h3index) RETURNS SETOF h3index AS $$ SELECT __h3_cell_to_children_aux($1, -1, -1) $$ LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_children_slow(index h3index) IS 'Slower version of H3ToChildren but allocates less memory'; -- regions ALTER FUNCTION h3_polyfill(exterior polygon, holes polygon[], resolution integer) RENAME TO h3_polygon_to_cells; ALTER FUNCTION h3_set_to_multi_polygon(h3index[], OUT exterior polygon, OUT holes polygon[]) RENAME TO h3_cells_to_multi_polygon; -- edge ALTER FUNCTION h3_indexes_are_neighbors(h3index, h3index) RENAME TO h3_are_neighbor_cells; CREATE OR REPLACE FUNCTION h3_are_neighbor_cells(origin h3index, destination h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ALTER FUNCTION h3_get_h3_unidirectional_edge(origin h3index, destination h3index) RENAME TO h3_cells_to_directed_edge; ALTER FUNCTION h3_unidirectional_edge_is_valid(edge h3index) RENAME TO h3_is_valid_directed_edge; ALTER FUNCTION h3_get_origin_h3_index_from_unidirectional_edge(edge h3index) RENAME TO h3_get_directed_edge_origin; ALTER FUNCTION h3_get_destination_h3_index_from_unidirectional_edge(edge h3index) RENAME TO h3_get_directed_edge_destination; ALTER FUNCTION h3_get_h3_indexes_from_unidirectional_edge(edge h3index, OUT origin h3index, OUT destination h3index) RENAME TO h3_directed_edge_to_cells; ALTER FUNCTION h3_get_h3_unidirectional_edges_from_hexagon(h3index) RENAME TO h3_origin_to_directed_edges; ALTER FUNCTION h3_get_h3_unidirectional_edge_boundary(edge h3index) RENAME TO h3_directed_edge_to_boundary; -- miscellaneous ALTER FUNCTION h3_point_dist(a point, b point, unit text) RENAME TO h3_great_circle_distance; ALTER FUNCTION h3_hex_area(resolution integer, unit text) RENAME TO h3_get_hexagon_area_avg; DROP FUNCTION IF EXISTS h3_edge_length(resolution integer, unit text); CREATE OR REPLACE FUNCTION h3_get_hexagon_edge_length_avg(resolution integer, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_hexagon_edge_length_avg(integer, text) IS 'Average hexagon edge length in (kilo)meters at the given resolution'; ALTER FUNCTION h3_exact_edge_length(edge h3index, unit text) RENAME TO h3_edge_length; ALTER FUNCTION h3_num_hexagons(resolution integer) RENAME TO h3_get_num_cells; ALTER FUNCTION h3_get_res_0_indexes() RENAME TO h3_get_res_0_cells; ALTER FUNCTION h3_get_pentagon_indexes(resolution integer) RENAME TO h3_get_pentagons; -- deprecated DROP FUNCTION IF EXISTS h3_hex_area(integer, boolean); DROP FUNCTION IF EXISTS h3_edge_length(integer, boolean); -- copied from 07-vertex.sql CREATE OR REPLACE FUNCTION h3_cell_to_vertex(cell h3index, vertexNum integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_vertex(cell h3index, vertexNum integer) IS 'Returns a single vertex for a given cell, as an H3 index'; CREATE OR REPLACE FUNCTION h3_cell_to_vertexes(cell h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_vertexes(cell h3index) IS 'Returns all vertexes for a given cell, as H3 indexes'; CREATE OR REPLACE FUNCTION h3_vertex_to_lat_lng(vertex h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_vertex_to_lat_lng(vertex h3index) IS 'Get the geocoordinates of an H3 vertex'; CREATE OR REPLACE FUNCTION h3_is_valid_vertex(vertex h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_valid_vertex(vertex h3index) IS 'Whether the input is a valid H3 vertex'; h3-pg-4.2.2/h3/sql/updates/h3--4.0.0--4.0.1.sql000066400000000000000000000174441475234715600175230ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.0.1'" to load this file. \quit CREATE OR REPLACE FUNCTION h3_cell_to_boundary_wkb(cell h3index) RETURNS bytea AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary_wkb(h3index) IS 'Finds the boundary of the index, converts to EWKB. Splits polygons when crossing 180th meridian. This function has to return WKB since Postgres does not provide multipolygon type.'; -- deprecate extend flag DROP FUNCTION IF EXISTS h3_cell_to_boundary(cell h3index, extend_at_meridian boolean); CREATE OR REPLACE FUNCTION h3_cell_to_boundary(cell h3index, extend_antimeridian boolean) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary(h3index, boolean) IS 'DEPRECATED: Use `SET h3.extend_antimeridian TO true` instead.'; CREATE OR REPLACE FUNCTION h3_cell_to_boundary(cell h3index) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary(h3index) IS 'Finds the boundary of the index. Use `SET h3.extend_antimeridian TO true` to extend coordinates when crossing 180th meridian.'; -- fix comments -- indexing COMMENT ON FUNCTION h3_lat_lng_to_cell(point, integer) IS 'Indexes the location at the specified resolution.'; COMMENT ON FUNCTION h3_cell_to_lat_lng(h3index) IS 'Finds the centroid of the index.'; -- inspection COMMENT ON FUNCTION h3_get_resolution(h3index) IS 'Returns the resolution of the index.'; COMMENT ON FUNCTION h3_get_base_cell_number(h3index) IS 'Returns the base cell number of the index.'; COMMENT ON FUNCTION h3_is_valid_cell(h3index) IS 'Returns true if the given H3Index is valid.'; COMMENT ON FUNCTION h3_is_res_class_iii(h3index) IS 'Returns true if this index has a resolution with Class III orientation.'; COMMENT ON FUNCTION h3_is_pentagon(h3index) IS 'Returns true if this index represents a pentagonal cell.'; COMMENT ON FUNCTION h3_get_icosahedron_faces(h3index) IS 'Find all icosahedron faces intersected by a given H3 index.'; -- traversal COMMENT ON FUNCTION h3_grid_disk(h3index, integer) IS 'Produces indices within "k" distance of the origin index.'; COMMENT ON FUNCTION h3_grid_disk_distances(h3index, integer) IS 'Produces indices within "k" distance of the origin index paired with their distance to the origin.'; COMMENT ON FUNCTION h3_grid_ring_unsafe(h3index, integer) IS 'Returns the hollow hexagonal ring centered at origin with distance "k".'; COMMENT ON FUNCTION h3_grid_distance(h3index, h3index) IS 'Returns the distance in grid cells between the two indices.'; COMMENT ON FUNCTION h3_cell_to_local_ij(h3index, h3index) IS 'Produces local IJ coordinates for an H3 index anchored by an origin.'; COMMENT ON FUNCTION h3_local_ij_to_cell(h3index, point) IS 'Produces an H3 index from local IJ coordinates anchored by an origin.'; -- hierarchy COMMENT ON FUNCTION h3_cell_to_parent(cell h3index, resolution integer) IS 'Returns the parent of the given index.'; COMMENT ON FUNCTION h3_cell_to_children(cell h3index, resolution integer) IS 'Returns the set of children of the given index.'; COMMENT ON FUNCTION h3_cell_to_center_child(cell h3index, resolution integer) IS 'Returns the center child (finer) index contained by input index at given resolution.'; COMMENT ON FUNCTION h3_compact_cells(cells h3index[]) IS 'Compacts the given array as best as possible.'; COMMENT ON FUNCTION h3_cell_to_parent(cell h3index) IS 'Returns the parent of the given index.'; COMMENT ON FUNCTION h3_cell_to_children(cell h3index) IS 'Returns the set of children of the given index.'; COMMENT ON FUNCTION h3_cell_to_center_child(cell h3index) IS 'Returns the center child (finer) index contained by input index at next resolution.'; COMMENT ON FUNCTION h3_uncompact_cells(cells h3index[]) IS 'Uncompacts the given array at the resolution one higher than the highest resolution in the set.'; COMMENT ON FUNCTION h3_cell_to_children_slow(index h3index, resolution integer) IS 'Slower version of H3ToChildren but allocates less memory.'; COMMENT ON FUNCTION h3_cell_to_children_slow(index h3index) IS 'Slower version of H3ToChildren but allocates less memory.'; -- regions COMMENT ON FUNCTION h3_polygon_to_cells(polygon, polygon[], integer) IS 'Takes an exterior polygon [and a set of hole polygon] and returns the set of hexagons that best fit the structure.'; COMMENT ON FUNCTION h3_cells_to_multi_polygon(h3index[]) IS '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.'; -- edge COMMENT ON FUNCTION h3_are_neighbor_cells(origin h3index, destination h3index) IS 'Returns true if the given indices are neighbors.'; COMMENT ON FUNCTION h3_origin_to_directed_edges(h3index) IS 'Returns all unidirectional edges with the given index as origin.'; -- vertex COMMENT ON FUNCTION h3_cell_to_vertex(cell h3index, vertexNum integer) IS 'Returns a single vertex for a given cell, as an H3 index.'; COMMENT ON FUNCTION h3_cell_to_vertexes(cell h3index) IS 'Returns all vertexes for a given cell, as H3 indexes.'; COMMENT ON FUNCTION h3_vertex_to_lat_lng(vertex h3index) IS 'Get the geocoordinates of an H3 vertex.'; COMMENT ON FUNCTION h3_is_valid_vertex(vertex h3index) IS 'Whether the input is a valid H3 vertex.'; -- miscellaneous COMMENT ON FUNCTION h3_great_circle_distance(point, point, text) IS 'The great circle distance in radians between two spherical coordinates.'; COMMENT ON FUNCTION h3_get_hexagon_area_avg(integer, text) IS 'Average hexagon area in square (kilo)meters at the given resolution.'; COMMENT ON FUNCTION h3_cell_area(h3index, text) IS 'Exact area for a specific cell (hexagon or pentagon).'; COMMENT ON FUNCTION h3_get_hexagon_edge_length_avg(integer, text) IS 'Average hexagon edge length in (kilo)meters at the given resolution.'; COMMENT ON FUNCTION h3_edge_length(h3index, text) IS 'Exact length for a specific unidirectional edge.'; COMMENT ON FUNCTION h3_get_num_cells(integer) IS 'Number of unique H3 indexes at the given resolution.'; COMMENT ON FUNCTION h3_get_res_0_cells() IS 'Returns all 122 resolution 0 indexes.'; COMMENT ON FUNCTION h3_get_pentagons(resolution integer) IS 'All the pentagon H3 indexes at the specified resolution.'; -- operators COMMENT ON OPERATOR = (h3index, h3index) IS 'Returns true if two indexes are the same.'; COMMENT ON OPERATOR && (h3index, h3index) IS 'Returns true if the two H3 indexes intersect.'; COMMENT ON OPERATOR @> (h3index, h3index) IS 'Returns true if A containts B.'; COMMENT ON OPERATOR <@ (h3index, h3index) IS 'Returns true if A is contained by B.'; COMMENT ON OPERATOR <-> (h3index, h3index) IS 'Returns the distance in grid cells between the two indices.'; -- casts COMMENT ON CAST (h3index AS bigint) IS 'Convert H3 index to bigint, which is useful when you need a decimal representation.'; COMMENT ON CAST (h3index AS bigint) IS 'Convert bigint to H3 index.'; COMMENT ON CAST (h3index AS point) IS 'Convert H3 index to point.'; h3-pg-4.2.2/h3/sql/updates/h3--4.0.1--4.0.2.sql000066400000000000000000000462741475234715600175300ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.0.2'" to load this file. \quit -- Due to mishandling of C-level renames in previous releases, -- this upgrade attempts to align user-installations as much as possible -- by copying the full-install script in its entirety -- (except casts/operators/etc) -- -- For more information see PR #87 CREATE OR REPLACE FUNCTION h3index_in(cstring) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_out(h3index) RETURNS cstring AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_lat_lng_to_cell(latlng point, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_lat_lng_to_cell(point, integer) IS 'Indexes the location at the specified resolution.'; CREATE OR REPLACE FUNCTION h3_cell_to_lat_lng(cell h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_lat_lng(h3index) IS 'Finds the centroid of the index.'; CREATE OR REPLACE FUNCTION h3_cell_to_boundary(cell h3index) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary(h3index) IS 'Finds the boundary of the index. Use `SET h3.extend_antimeridian TO true` to extend coordinates when crossing 180th meridian.'; CREATE OR REPLACE FUNCTION h3_get_resolution(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_resolution(h3index) IS 'Returns the resolution of the index.'; CREATE OR REPLACE FUNCTION h3_get_base_cell_number(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_base_cell_number(h3index) IS 'Returns the base cell number of the index.'; CREATE OR REPLACE FUNCTION h3_is_valid_cell(h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_valid_cell(h3index) IS 'Returns true if the given H3Index is valid.'; CREATE OR REPLACE FUNCTION h3_is_res_class_iii(h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_res_class_iii(h3index) IS 'Returns true if this index has a resolution with Class III orientation.'; CREATE OR REPLACE FUNCTION h3_is_pentagon(h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_pentagon(h3index) IS 'Returns true if this index represents a pentagonal cell.'; CREATE OR REPLACE FUNCTION h3_get_icosahedron_faces(h3index) RETURNS integer[] AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_icosahedron_faces(h3index) IS 'Find all icosahedron faces intersected by a given H3 index.'; CREATE OR REPLACE FUNCTION h3_grid_disk(origin h3index, k integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_disk(h3index, integer) IS 'Produces indices within "k" distance of the origin index.'; CREATE OR REPLACE FUNCTION h3_grid_disk_distances(origin h3index, k integer DEFAULT 1, OUT index h3index, OUT distance int) RETURNS SETOF record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_disk_distances(h3index, integer) IS 'Produces indices within "k" distance of the origin index paired with their distance to the origin.'; CREATE OR REPLACE FUNCTION h3_grid_ring_unsafe(origin h3index, k integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_ring_unsafe(h3index, integer) IS 'Returns the hollow hexagonal ring centered at origin with distance "k".'; CREATE OR REPLACE FUNCTION h3_grid_path_cells(origin h3index, destination h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_path_cells(h3index, h3index) IS '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.'; CREATE OR REPLACE FUNCTION h3_grid_distance(origin h3index, destination h3index) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_grid_distance(h3index, h3index) IS 'Returns the distance in grid cells between the two indices.'; CREATE OR REPLACE FUNCTION h3_cell_to_local_ij(origin h3index, index h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_local_ij(h3index, h3index) IS 'Produces local IJ coordinates for an H3 index anchored by an origin.'; CREATE OR REPLACE FUNCTION h3_local_ij_to_cell(origin h3index, coord point) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_local_ij_to_cell(h3index, point) IS 'Produces an H3 index from local IJ coordinates anchored by an origin.'; CREATE OR REPLACE FUNCTION h3_cell_to_parent(cell h3index, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_parent(cell h3index, resolution integer) IS 'Returns the parent of the given index.'; CREATE OR REPLACE FUNCTION h3_cell_to_children(cell h3index, resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_children(cell h3index, resolution integer) IS 'Returns the set of children of the given index.'; CREATE OR REPLACE FUNCTION h3_cell_to_center_child(cell h3index, resolution integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_center_child(cell h3index, resolution integer) IS 'Returns the center child (finer) index contained by input index at given resolution.'; CREATE OR REPLACE FUNCTION h3_compact_cells(cells h3index[]) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_compact_cells(cells h3index[]) IS 'Compacts the given array as best as possible.'; CREATE OR REPLACE FUNCTION h3_uncompact_cells(cells h3index[], resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_uncompact_cells(cells h3index[], resolution integer) IS 'Uncompacts the given array at the given resolution.'; CREATE OR REPLACE FUNCTION h3_cell_to_parent(cell h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_parent(cell h3index) IS 'Returns the parent of the given index.'; CREATE OR REPLACE FUNCTION h3_cell_to_children(cell h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_children(cell h3index) IS 'Returns the set of children of the given index.'; CREATE OR REPLACE FUNCTION h3_cell_to_center_child(cell h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_center_child(cell h3index) IS 'Returns the center child (finer) index contained by input index at next resolution.'; CREATE OR REPLACE FUNCTION h3_uncompact_cells(cells h3index[]) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_uncompact_cells(cells h3index[]) IS 'Uncompacts the given array at the resolution one higher than the highest resolution in the set.'; CREATE OR REPLACE FUNCTION __h3_cell_to_children_aux(index h3index, resolution integer, current integer) RETURNS SETOF h3index AS $$ DECLARE retSet h3index[]; r h3index; BEGIN IF current = -1 THEN SELECT h3_get_resolution(index) into current; END IF; IF resolution = -1 THEN SELECT h3_get_resolution(index)+1 into resolution; END IF; IF current < resolution THEN SELECT ARRAY(SELECT h3_cell_to_children(index)) into retSet; FOREACH r in ARRAY retSet LOOP RETURN QUERY SELECT __h3_cell_to_children_aux(r, resolution, current + 1); END LOOP; ELSE RETURN NEXT index; END IF; END;$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION h3_cell_to_children_slow(index h3index, resolution integer) RETURNS SETOF h3index AS $$ SELECT __h3_cell_to_children_aux($1, $2, -1) $$ LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_children_slow(index h3index, resolution integer) IS 'Slower version of H3ToChildren but allocates less memory.'; CREATE OR REPLACE FUNCTION h3_cell_to_children_slow(index h3index) RETURNS SETOF h3index AS $$ SELECT __h3_cell_to_children_aux($1, -1, -1) $$ LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_children_slow(index h3index) IS 'Slower version of H3ToChildren but allocates less memory.'; CREATE OR REPLACE FUNCTION h3_polygon_to_cells(exterior polygon, holes polygon[], resolution integer DEFAULT 1) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE -- intentionally NOT STRICT CALLED ON NULL INPUT PARALLEL SAFE; COMMENT ON FUNCTION h3_polygon_to_cells(polygon, polygon[], integer) IS 'Takes an exterior polygon [and a set of hole polygon] and returns the set of hexagons that best fit the structure.'; CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon(h3index[], OUT exterior polygon, OUT holes polygon[]) RETURNS SETOF record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cells_to_multi_polygon(h3index[]) IS '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.'; CREATE OR REPLACE FUNCTION h3_are_neighbor_cells(origin h3index, destination h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_are_neighbor_cells(origin h3index, destination h3index) IS 'Returns true if the given indices are neighbors.'; CREATE OR REPLACE FUNCTION h3_cells_to_directed_edge(origin h3index, destination h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cells_to_directed_edge(origin h3index, destination h3index) IS 'Returns a unidirectional edge H3 index based on the provided origin and destination.'; CREATE OR REPLACE FUNCTION h3_is_valid_directed_edge(edge h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_valid_directed_edge(edge h3index) IS 'Returns true if the given edge is valid.'; CREATE OR REPLACE FUNCTION h3_get_directed_edge_origin(edge h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_directed_edge_origin(edge h3index) IS 'Returns the origin index from the given edge.'; CREATE OR REPLACE FUNCTION h3_get_directed_edge_destination(edge h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_directed_edge_destination(edge h3index) IS 'Returns the destination index from the given edge.'; CREATE OR REPLACE FUNCTION h3_directed_edge_to_cells(edge h3index, OUT origin h3index, OUT destination h3index) RETURNS record AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_directed_edge_to_cells(edge h3index) IS 'Returns the pair of indices from the given edge.'; CREATE OR REPLACE FUNCTION h3_origin_to_directed_edges(h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_origin_to_directed_edges(h3index) IS 'Returns all unidirectional edges with the given index as origin.'; CREATE OR REPLACE FUNCTION h3_directed_edge_to_boundary(edge h3index) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_directed_edge_to_boundary(edge h3index) IS 'Provides the coordinates defining the unidirectional edge.'; CREATE OR REPLACE FUNCTION h3_cell_to_vertex(cell h3index, vertexNum integer) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_vertex(cell h3index, vertexNum integer) IS 'Returns a single vertex for a given cell, as an H3 index.'; CREATE OR REPLACE FUNCTION h3_cell_to_vertexes(cell h3index) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_vertexes(cell h3index) IS 'Returns all vertexes for a given cell, as H3 indexes.'; CREATE OR REPLACE FUNCTION h3_vertex_to_lat_lng(vertex h3index) RETURNS point AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_vertex_to_lat_lng(vertex h3index) IS 'Get the geocoordinates of an H3 vertex.'; CREATE OR REPLACE FUNCTION h3_is_valid_vertex(vertex h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_is_valid_vertex(vertex h3index) IS 'Whether the input is a valid H3 vertex.'; CREATE OR REPLACE FUNCTION h3_great_circle_distance(a point, b point, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_great_circle_distance(point, point, text) IS 'The great circle distance in radians between two spherical coordinates.'; CREATE OR REPLACE FUNCTION h3_get_hexagon_area_avg(resolution integer, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_hexagon_area_avg(integer, text) IS 'Average hexagon area in square (kilo)meters at the given resolution.'; CREATE OR REPLACE FUNCTION h3_cell_area(cell h3index, unit text DEFAULT 'km^2') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_area(h3index, text) IS 'Exact area for a specific cell (hexagon or pentagon).'; CREATE OR REPLACE FUNCTION h3_get_hexagon_edge_length_avg(resolution integer, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_hexagon_edge_length_avg(integer, text) IS 'Average hexagon edge length in (kilo)meters at the given resolution.'; CREATE OR REPLACE FUNCTION h3_edge_length(edge h3index, unit text DEFAULT 'km') RETURNS double precision AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_edge_length(h3index, text) IS 'Exact length for a specific unidirectional edge.'; CREATE OR REPLACE FUNCTION h3_get_num_cells(resolution integer) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_num_cells(integer) IS 'Number of unique H3 indexes at the given resolution.'; CREATE OR REPLACE FUNCTION h3_get_res_0_cells() RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_res_0_cells() IS 'Returns all 122 resolution 0 indexes.'; CREATE OR REPLACE FUNCTION h3_get_pentagons(resolution integer) RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_pentagons(resolution integer) IS 'All the pentagon H3 indexes at the specified resolution.'; CREATE OR REPLACE FUNCTION h3index_eq(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_ne(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_lt(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_le(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_gt(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_ge(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_overlaps(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON OPERATOR && (h3index, h3index) IS 'Returns true if the two H3 indexes intersect.'; CREATE OR REPLACE FUNCTION h3index_contains(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON OPERATOR @> (h3index, h3index) IS 'Returns true if A containts B.'; CREATE OR REPLACE FUNCTION h3index_contained_by(h3index, h3index) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON OPERATOR <@ (h3index, h3index) IS 'Returns true if A is contained by B.'; COMMENT ON OPERATOR <-> (h3index, h3index) IS 'Returns the distance in grid cells between the two indices.'; CREATE OR REPLACE FUNCTION h3index_cmp(h3index, h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_sortsupport(internal) RETURNS void AS 'h3', 'h3index_sortsupport' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_hash(h3index) RETURNS integer AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_hash_extended(h3index, int8) RETURNS int8 AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_to_bigint(h3index) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON CAST (h3index AS bigint) IS 'Convert H3 index to bigint, which is useful when you need a decimal representation.'; CREATE OR REPLACE FUNCTION bigint_to_h3index(bigint) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON CAST (h3index AS bigint) IS 'Convert bigint to H3 index.'; COMMENT ON CAST (h3index AS point) IS 'Convert H3 index to point.'; CREATE OR REPLACE FUNCTION h3_get_extension_version() RETURNS text AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_get_extension_version() IS 'Get the currently installed version of the extension.'; CREATE OR REPLACE FUNCTION h3_cell_to_boundary_wkb(cell h3index) RETURNS bytea AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary_wkb(h3index) IS 'Finds the boundary of the index, converts to EWKB. Splits polygons when crossing 180th meridian. This function has to return WKB since Postgres does not provide multipolygon type.'; CREATE OR REPLACE FUNCTION h3_cell_to_boundary(cell h3index, extend_antimeridian boolean) RETURNS polygon AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary(h3index, boolean) IS 'DEPRECATED: Use `SET h3.extend_antimeridian TO true` instead.'; h3-pg-4.2.2/h3/sql/updates/h3--4.0.2--4.0.3.sql000066400000000000000000000030531475234715600175160ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.0.3'" to load this file. \quit CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon_wkb(h3index[]) RETURNS bytea AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cells_to_multi_polygon_wkb(h3index[]) IS 'Create a LinkedGeoPolygon describing the outline(s) of a set of hexagons, converts to EWKB. Splits polygons when crossing 180th meridian.'; -- BRIN operator class CREATE OPERATOR CLASS h3index_minmax_ops DEFAULT FOR TYPE h3index USING brin AS OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 brin_minmax_opcinfo(internal), FUNCTION 2 brin_minmax_add_value(internal, internal, internal, internal), FUNCTION 3 brin_minmax_consistent(internal, internal, internal), FUNCTION 4 brin_minmax_union(internal, internal, internal); h3-pg-4.2.2/h3/sql/updates/h3--4.0.3--4.1.0.sql000066400000000000000000000057131475234715600175220ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.1.0'" to load this file. \quit DROP FUNCTION IF EXISTS h3_cell_to_boundary_wkb(h3index); DROP FUNCTION IF EXISTS h3_cells_to_multi_polygon_wkb(h3index[]); ALTER OPERATOR FAMILY btree_h3index_ops USING btree RENAME TO h3index_ops; ALTER OPERATOR CLASS btree_h3index_ops USING btree RENAME TO h3index_ops; ALTER OPERATOR FAMILY hash_h3index_ops USING hash RENAME TO h3index_ops; ALTER OPERATOR CLASS hash_h3index_ops USING hash RENAME TO h3index_ops; CREATE OR REPLACE FUNCTION h3_pg_migrate_pass_by_reference(h3index) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_pg_migrate_pass_by_reference(h3index) IS 'Migrate h3index from pass-by-reference to pass-by-value.'; -- make distance operator allow different resolutions CREATE OR REPLACE FUNCTION h3index_distance(h3index, h3index) RETURNS bigint AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; DROP OPERATOR IF EXISTS <-> (h3index, h3index); CREATE OPERATOR <-> ( LEFTARG = h3index, RIGHTARG = h3index, PROCEDURE = h3index_distance, COMMUTATOR = <-> ); COMMENT ON OPERATOR <-> (h3index, h3index) IS 'Returns the distance in grid cells between the two indices (at the lowest resolution of the two).'; -- new child pos functions -- see https://github.com/uber/h3/pull/719 CREATE OR REPLACE FUNCTION h3_cell_to_child_pos(child h3index, parentRes integer) RETURNS int8 AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_child_pos(child h3index, parentRes integer) IS 'Returns the position of the child cell within an ordered list of all children of the cells parent at the specified resolution parentRes. The order of the ordered list is the same as that returned by cellToChildren. This is the complement of childPosToCell.'; CREATE OR REPLACE FUNCTION h3_child_pos_to_cell(childPos int8, parent h3index, childRes int) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_child_pos_to_cell(childPos int8, parent h3index, childRes int) IS 'Returns the child cell at a given position within an ordered list of all children of parent at the specified resolution childRes. The order of the ordered list is the same as that returned by cellToChildren. This is the complement of cellToChildPos.'; h3-pg-4.2.2/h3/sql/updates/h3--4.1.0--4.1.1.sql000066400000000000000000000013571475234715600175210ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.1.1'" to load this file. \quit h3-pg-4.2.2/h3/sql/updates/h3--4.1.1--4.1.2.sql000066400000000000000000000013571475234715600175230ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.1.2'" to load this file. \quit h3-pg-4.2.2/h3/sql/updates/h3--4.1.2--4.1.3.sql000066400000000000000000000014771475234715600175300ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.1.3'" to load this file. \quit COMMENT ON OPERATOR @> (h3index, h3index) IS 'Returns true if A contains B.';h3-pg-4.2.2/h3/sql/updates/h3--4.1.3--4.1.4.sql000066400000000000000000000020641475234715600175230ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.1.4'" to load this file. \quit CREATE OR REPLACE FUNCTION h3index_recv(internal) RETURNS h3index AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_send(h3index) RETURNS bytea AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ALTER TYPE h3index SET ( RECEIVE = h3index_recv, SEND = h3index_send ); h3-pg-4.2.2/h3/sql/updates/h3--4.1.4--4.2.0.sql000066400000000000000000000047371475234715600175320ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.2.0'" to load this file. \quit --@ availability: 4.2.0 CREATE OR REPLACE FUNCTION h3_polygon_to_cells_experimental(exterior polygon, holes polygon[], resolution integer DEFAULT 1, containment_mode text DEFAULT 'center') RETURNS SETOF h3index AS 'h3' LANGUAGE C IMMUTABLE -- intentionally NOT STRICT CALLED ON NULL INPUT PARALLEL SAFE; COMMENT ON FUNCTION h3_polygon_to_cells_experimental(polygon, polygon[], integer, text) IS 'Takes an exterior polygon [and a set of hole polygon] and returns the set of hexagons that best fit the structure.'; -- SP-GiST operator class CREATE OR REPLACE FUNCTION h3index_spgist_config(internal, internal) RETURNS void AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_spgist_choose(internal, internal) RETURNS void AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_spgist_picksplit(internal, internal) RETURNS void AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_spgist_inner_consistent(internal, internal) RETURNS void AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3index_spgist_leaf_consistent(internal, internal) RETURNS boolean AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; -- intentionally *not* marked as DEFAULT CREATE OPERATOR CLASS h3index_ops_experimental FOR TYPE h3index USING spgist AS OPERATOR 6 = , OPERATOR 7 @> , OPERATOR 8 <@ , FUNCTION 1 h3index_spgist_config(internal, internal), FUNCTION 2 h3index_spgist_choose(internal, internal), FUNCTION 3 h3index_spgist_picksplit(internal, internal), FUNCTION 4 h3index_spgist_inner_consistent(internal, internal), FUNCTION 5 h3index_spgist_leaf_consistent(internal, internal); h3-pg-4.2.2/h3/sql/updates/h3--4.2.0--4.2.1.sql000066400000000000000000000013571475234715600175230ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.2.1'" to load this file. \quit h3-pg-4.2.2/h3/sql/updates/h3--4.2.1--4.2.2.sql000066400000000000000000000013571475234715600175250ustar00rootroot00000000000000/* * Copyright 2025 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO '4.2.2'" to load this file. \quit h3-pg-4.2.2/h3/src/000077500000000000000000000000001475234715600135545ustar00rootroot00000000000000h3-pg-4.2.2/h3/src/binding/000077500000000000000000000000001475234715600151665ustar00rootroot00000000000000h3-pg-4.2.2/h3/src/binding/edge.c000066400000000000000000000114441475234715600162420ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_INFO_V1 #include // SRF_IS_FIRSTCALL #include // HeapTuple #include // PG_RETURN_POLYGON_P #include "error.h" #include "type.h" #include "srf.h" #if POSTGRESQL_VERSION_MAJOR >= 16 #include "varatt.h" //VAR_SIZE and friends moved to here from postgres.h #endif PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_are_neighbor_cells); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cells_to_directed_edge); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_is_valid_directed_edge); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_directed_edge_origin); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_directed_edge_destination); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_directed_edge_to_cells); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_origin_to_directed_edges); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_directed_edge_to_boundary); /* Returns whether or not the provided H3 cell indexes are neighbors. */ Datum h3_are_neighbor_cells(PG_FUNCTION_ARGS) { int neighboring; H3Index origin = PG_GETARG_H3INDEX(0); H3Index destination = PG_GETARG_H3INDEX(1); h3_assert(areNeighborCells(origin, destination, &neighboring)); PG_RETURN_BOOL(neighboring); } /* Returns a unidirectional edge H3 index based on the provided origin and destination. */ Datum h3_cells_to_directed_edge(PG_FUNCTION_ARGS) { H3Index edge; H3Index origin = PG_GETARG_H3INDEX(0); H3Index destination = PG_GETARG_H3INDEX(1); h3_assert(cellsToDirectedEdge(origin, destination, &edge)); PG_RETURN_H3INDEX(edge); } /* Determines if the provided H3Index is a valid unidirectional edge index. */ Datum h3_is_valid_directed_edge(PG_FUNCTION_ARGS) { H3Index edge = PG_GETARG_H3INDEX(0); int valid = isValidDirectedEdge(edge); PG_RETURN_BOOL(valid); } /* Returns the origin hexagon from the unidirectional edge H3Index. */ Datum h3_get_directed_edge_origin(PG_FUNCTION_ARGS) { H3Index origin; H3Index edge = PG_GETARG_H3INDEX(0); h3_assert(getDirectedEdgeOrigin(edge, &origin)); PG_RETURN_H3INDEX(origin); } /* Returns the destination hexagon from the unidirectional edge H3Index. */ Datum h3_get_directed_edge_destination(PG_FUNCTION_ARGS) { H3Index destination; H3Index edge = PG_GETARG_H3INDEX(0); h3_assert(getDirectedEdgeDestination(edge, &destination)); PG_RETURN_H3INDEX(destination); } /* Returns the origin, destination pair of hexagon IDs for the given edge ID. */ Datum h3_directed_edge_to_cells(PG_FUNCTION_ARGS) { TupleDesc tuple_desc; Datum values[2]; bool nulls[2] = {false}; HeapTuple tuple; Datum result; H3Index edge = PG_GETARG_H3INDEX(0); H3Index *cells = palloc(2 * sizeof(H3Index)); h3_assert(directedEdgeToCells(edge, cells)); ENSURE_TYPEFUNC_COMPOSITE(get_call_result_type(fcinfo, NULL, &tuple_desc)); values[0] = H3IndexGetDatum(cells[0]); values[1] = H3IndexGetDatum(cells[1]); tuple_desc = BlessTupleDesc(tuple_desc); tuple = heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); PG_RETURN_DATUM(result); } /* Provides all of the unidirectional edges from the current H3Index. */ Datum h3_origin_to_directed_edges(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); int max = 6; H3Index origin = PG_GETARG_H3INDEX(0); H3Index *edges = palloc(max * sizeof(H3Index)); h3_assert(originToDirectedEdges(origin, edges)); funcctx->user_fctx = edges; funcctx->max_calls = max; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* Provides the coordinates defining the unidirectional edge. */ Datum h3_directed_edge_to_boundary(PG_FUNCTION_ARGS) { CellBoundary boundary; POLYGON *polygon; int size; H3Index edge = PG_GETARG_H3INDEX(0); h3_assert(directedEdgeToBoundary(edge, &boundary)); size = offsetof(POLYGON, p[0]) +sizeof(polygon->p[0]) * boundary.numVerts; polygon = (POLYGON *) palloc(size); SET_VARSIZE(polygon, size); polygon->npts = boundary.numVerts; for (int v = 0; v < boundary.numVerts; v++) { polygon->p[v].x = radsToDegs(boundary.verts[v].lat); polygon->p[v].y = radsToDegs(boundary.verts[v].lng); } PG_RETURN_POLYGON_P(polygon); } h3-pg-4.2.2/h3/src/binding/hierarchy.c000066400000000000000000000142021475234715600173070ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_INFO_V1 #include // SRF_IS_FIRSTCALL #include // ArrayType #include "error.h" #include "type.h" #include "srf.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_parent); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_children); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_center_child); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_child_pos); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_child_pos_to_cell); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_compact_cells); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_uncompact_cells); /* Returns the parent (coarser) index containing given index */ Datum h3_cell_to_parent(PG_FUNCTION_ARGS) { H3Index parent; H3Index origin = PG_GETARG_H3INDEX(0); int resolution = PG_GETARG_OPTIONAL_RES(1, origin, -1); h3_assert(cellToParent(origin, resolution, &parent)); PG_RETURN_H3INDEX(parent); } /* Returns children indexes at given resolution (or next resolution if none given) */ Datum h3_cell_to_children(PG_FUNCTION_ARGS) { /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { int64_t max; int64_t size; H3Index *children; /* create a function context for cross-call persistence */ FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); /* switch to memory context appropriate for multiple function calls */ MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* BEGIN One-time setup code */ /* ensure valid resolution target */ H3Index origin = PG_GETARG_H3INDEX(0); int resolution = PG_GETARG_OPTIONAL_RES(1, origin, 1); h3_assert(cellToChildrenSize(origin, resolution, &max)); size = max * sizeof(H3Index); ASSERT( AllocSizeIsValid(size), ERRCODE_OUT_OF_MEMORY, "Cannot allocate necessary amount memory, try using h3_cell_to_children_slow()" ); children = palloc(size); h3_assert(cellToChildren(origin, resolution, children)); funcctx->user_fctx = children; funcctx->max_calls = max; /* END One-time setup code */ MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* Returns the center child (finer) index contained by input index at given resolution */ Datum h3_cell_to_center_child(PG_FUNCTION_ARGS) { H3Index child; H3Index origin = PG_GETARG_H3INDEX(0); int resolution = PG_GETARG_OPTIONAL_RES(1, origin, 1); h3_assert(cellToCenterChild(origin, resolution, &child)); PG_RETURN_H3INDEX(child); } Datum h3_cell_to_child_pos(PG_FUNCTION_ARGS) { H3Index child = PG_GETARG_H3INDEX(0); int parentRes = PG_GETARG_INT32(1); int64_t childPos; h3_assert(cellToChildPos(child, parentRes, &childPos)); PG_RETURN_INT64(childPos); } Datum h3_child_pos_to_cell(PG_FUNCTION_ARGS) { int64_t childPos = PG_GETARG_INT64(0); H3Index parent = PG_GETARG_H3INDEX(1); int childRes = PG_GETARG_INT32(2); H3Index child; h3_assert(childPosToCell(childPos, parent, childRes, &child)); PG_RETURN_H3INDEX(child); } Datum h3_compact_cells(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { Datum value; bool isnull; int i = 0; FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); int max = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); ArrayIterator iterator = array_create_iterator(array, 0, NULL); H3Index *h3set = palloc(max * sizeof(H3Index)); H3Index *compactedSet = palloc0(max * sizeof(H3Index)); /* Extract data from array into h3set, and wipe compactedSet memory */ while (array_iterate(iterator, &value, &isnull)) { h3set[i++] = DatumGetH3Index(value); } h3_assert(compactCells(h3set, compactedSet, max)); funcctx->user_fctx = compactedSet; funcctx->max_calls = max; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } Datum h3_uncompact_cells(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { int resolution; Datum value; bool isnull; int i = 0; int64_t max; H3Index *uncompactedSet; FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); int numCompacted = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); ArrayIterator iterator = array_create_iterator(array, 0, NULL); H3Index *compactedSet = palloc(numCompacted * sizeof(H3Index)); /* * Extract data from array into compactedSet, and wipe compactedSet * memory */ while (array_iterate(iterator, &value, &isnull)) { compactedSet[i++] = DatumGetH3Index(value); } if (PG_NARGS() == 2) { resolution = PG_GETARG_INT32(1); } else { /* resolution parameter not set */ int highRes = 0; /* Find highest resolution in the given set */ for (int i = 0; i < numCompacted; i++) { int curRes = getResolution(compactedSet[i]); if (curRes > highRes) highRes = curRes; } /* * If the highest resolution is the maximun allowed, uncompact to * that */ /* Else uncompact one step further than the highest resolution */ resolution = (highRes == 15 ? highRes : highRes + 1); } h3_assert(uncompactCellsSize(compactedSet, numCompacted, resolution, &max)); uncompactedSet = palloc0(max * sizeof(H3Index)); h3_assert(uncompactCells(compactedSet, numCompacted, uncompactedSet, max, resolution)); funcctx->user_fctx = uncompactedSet; funcctx->max_calls = max; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } h3-pg-4.2.2/h3/src/binding/indexing.c000066400000000000000000000065571475234715600171540ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #if POSTGRESQL_VERSION_MAJOR >= 16 #include "varatt.h" //VAR_SIZE and friends moved to here from postgres.h #endif #include #include // PG_FUNCTION_INFO_V1 #include // PG_GETARG_POINT_P #include // fabs #include "constants.h" #include "error.h" #include "type.h" #include "guc.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_lat_lng_to_cell); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_lat_lng); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_boundary); /* Indexes the location at the specified resolution */ Datum h3_lat_lng_to_cell(PG_FUNCTION_ARGS) { H3Index cell; LatLng location; Point *point = PG_GETARG_POINT_P(0); int resolution = PG_GETARG_INT32(1); if (h3_guc_strict) { ASSERT( point->x >= -180 && point->x <= 180, ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, "Longitude must be between -180 and 180 degrees inclusive, but got %f.", point->x ); ASSERT( point->y >= -90 && point->y <= 90, ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, "Latitude must be between -90 and 90 degrees inclusive, but got %f.", point->y ); } location.lng = degsToRads(point->x); location.lat = degsToRads(point->y); h3_assert(latLngToCell(&location, resolution, &cell)); PG_FREE_IF_COPY(point, 0); PG_RETURN_H3INDEX(cell); } /* Finds the centroid of the index */ Datum h3_cell_to_lat_lng(PG_FUNCTION_ARGS) { LatLng center; Point *point = palloc(sizeof(Point)); H3Index cell = PG_GETARG_H3INDEX(0); h3_assert(cellToLatLng(cell, ¢er)); point->x = radsToDegs(center.lng); point->y = radsToDegs(center.lat); PG_RETURN_POINT_P(point); } /* Finds the boundary of the index */ Datum h3_cell_to_boundary(PG_FUNCTION_ARGS) { H3Index cell = PG_GETARG_H3INDEX(0); double delta, firstLon, lon, lat; int size; POLYGON *polygon; CellBoundary boundary; /* DEPRECATION BEGIN: Remove next major */ bool extend; if (PG_NARGS() == 1) { extend = h3_guc_extend_antimeridian; } else { extend = PG_GETARG_BOOL(1); H3_DEPRECATION("Please use `SET h3.extend_antimeridian TO true` instead of extend flag"); } /* DEPRECATION END */ h3_assert(cellToBoundary(cell, &boundary)); size = offsetof(POLYGON, p) +sizeof(polygon->p[0]) * boundary.numVerts; polygon = (POLYGON *) palloc(size); SET_VARSIZE(polygon, size); polygon->npts = boundary.numVerts; firstLon = boundary.verts[0].lng; if (firstLon < 0) { delta = -2 * M_PI; } else { delta = +2 * M_PI; } for (int v = 0; v < boundary.numVerts; v++) { lon = boundary.verts[v].lng; lat = boundary.verts[v].lat; /* check if different sign */ if (extend && fabs(lon - firstLon) > M_PI) lon = lon + delta; polygon->p[v].x = radsToDegs(lon); polygon->p[v].y = radsToDegs(lat); } PG_RETURN_POLYGON_P(polygon); } h3-pg-4.2.2/h3/src/binding/inspection.c000066400000000000000000000060451475234715600175120ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // ArrayType #include // get_typlenbyvalalign #include // INT4OID #include "error.h" #include "type.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_resolution); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_base_cell_number); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_is_valid_cell); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_is_res_class_iii); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_is_pentagon); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_icosahedron_faces); /* Returns the resolution of the index */ Datum h3_get_resolution(PG_FUNCTION_ARGS) { H3Index hex = PG_GETARG_H3INDEX(0); int32_t resolution = getResolution(hex); PG_RETURN_INT32(resolution); } /* Returns the base cell number of the index */ Datum h3_get_base_cell_number(PG_FUNCTION_ARGS) { H3Index hex = PG_GETARG_H3INDEX(0); int32_t result = getBaseCellNumber(hex); PG_RETURN_INT32(result); } /* Returns true if this is a valid H3 index */ Datum h3_is_valid_cell(PG_FUNCTION_ARGS) { H3Index hex = PG_GETARG_H3INDEX(0); bool result = isValidCell(hex); PG_RETURN_BOOL(result); } /* Returns true if this index has a resolution with Class III orientation */ Datum h3_is_res_class_iii(PG_FUNCTION_ARGS) { H3Index hex = PG_GETARG_H3INDEX(0); bool result = isResClassIII(hex); PG_RETURN_BOOL(result); } /* Returns true if this hex represents a pentagonal cell */ Datum h3_is_pentagon(PG_FUNCTION_ARGS) { H3Index hex = PG_GETARG_H3INDEX(0); bool result = isPentagon(hex); PG_RETURN_BOOL(result); } /* Find all icosahedron faces intersected by a given H3 index */ Datum h3_get_icosahedron_faces(PG_FUNCTION_ARGS) { Oid elmtype = INT4OID; int16 elmlen; bool elmbyval; char elmalign; int *faces; Datum *elements; int maxFaces; ArrayType *result; int nelems = 0; H3Index hex = PG_GETARG_H3INDEX(0); h3_assert(maxFaceCount(hex, &maxFaces)); /* get the faces */ faces = palloc(maxFaces * sizeof(int)); elements = palloc(maxFaces * sizeof(Datum)); h3_assert(getIcosahedronFaces(hex, faces)); for (int i = 0; i < maxFaces; i++) { int face = faces[i]; /* add any valid face to result array */ if (face > -1) elements[nelems++] = Int32GetDatum(face); } /* build the array */ get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign); result = construct_array(elements, nelems, elmtype, elmlen, elmbyval, elmalign); PG_RETURN_ARRAYTYPE_P(result); } h3-pg-4.2.2/h3/src/binding/miscellaneous.c000066400000000000000000000127611475234715600202040ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // SRF_IS_FIRSTCALL #include // PG_GETARG_POINT_P #include // text_to_cstring #include "error.h" #include "type.h" #include "srf.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_hexagon_area_avg); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_area); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_hexagon_edge_length_avg); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_edge_length); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_num_cells); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_res_0_cells); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_pentagons); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_great_circle_distance); /* Average hexagon area in square (kilo)meters at the given resolution */ Datum h3_get_hexagon_area_avg(PG_FUNCTION_ARGS) { int resolution = PG_GETARG_INT32(0); char *unit = text_to_cstring(PG_GETARG_TEXT_PP(1)); double area; if (strcmp(unit, "km") == 0) h3_assert(getHexagonAreaAvgKm2(resolution, &area)); else if (strcmp(unit, "m") == 0) h3_assert(getHexagonAreaAvgM2(resolution, &area)); else ASSERT(0, ERRCODE_INVALID_PARAMETER_VALUE, "Unit must be m or km."); PG_RETURN_FLOAT8(area); } /* Exact area for a specific cell (hexagon or pentagon) */ Datum h3_cell_area(PG_FUNCTION_ARGS) { H3Index cell = PG_GETARG_H3INDEX(0); char *unit = text_to_cstring(PG_GETARG_TEXT_PP(1)); double area; if (strcmp(unit, "rads^2") == 0) h3_assert(cellAreaRads2(cell, &area)); else if (strcmp(unit, "km^2") == 0) h3_assert(cellAreaKm2(cell, &area)); else if (strcmp(unit, "m^2") == 0) h3_assert(cellAreaM2(cell, &area)); else ASSERT(0, ERRCODE_INVALID_PARAMETER_VALUE, "Unit must be m^2, km^2 or rads^2."); PG_RETURN_FLOAT8(area); } /* Average hexagon edge length in (kilo)meters at the given resolution */ Datum h3_get_hexagon_edge_length_avg(PG_FUNCTION_ARGS) { int resolution = PG_GETARG_INT32(0); char *unit = text_to_cstring(PG_GETARG_TEXT_PP(1)); double length; if (strcmp(unit, "km") == 0) h3_assert(getHexagonEdgeLengthAvgKm(resolution, &length)); else if (strcmp(unit, "m") == 0) h3_assert(getHexagonEdgeLengthAvgM(resolution, &length)); else ASSERT(0, ERRCODE_INVALID_PARAMETER_VALUE, "Unit must be m or km."); PG_RETURN_FLOAT8(length); } /* Exact length for a specific unidirectional edge */ Datum h3_edge_length(PG_FUNCTION_ARGS) { H3Index edge = PG_GETARG_H3INDEX(0); char *unit = text_to_cstring(PG_GETARG_TEXT_PP(1)); double length; if (strcmp(unit, "rads") == 0) h3_assert(edgeLengthRads(edge, &length)); else if (strcmp(unit, "km") == 0) h3_assert(edgeLengthKm(edge, &length)); else if (strcmp(unit, "m") == 0) h3_assert(edgeLengthM(edge, &length)); else ASSERT(0, ERRCODE_INVALID_PARAMETER_VALUE, "Unit must be m, km or rads."); PG_RETURN_FLOAT8(length); } /* Number of unique H3 indexes at the given resolution */ Datum h3_get_num_cells(PG_FUNCTION_ARGS) { int64_t cells; int resolution = PG_GETARG_INT32(0); h3_assert(getNumCells(resolution, &cells)); PG_RETURN_INT64(cells); } /* Provides all resolution 0 indexes */ Datum h3_get_res_0_cells(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); int count = res0CellCount(); H3Index *indexes = palloc(count * sizeof(H3Index)); h3_assert(getRes0Cells(indexes)); funcctx->user_fctx = indexes; funcctx->max_calls = count; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* All the pentagon H3 indexes at the specified resolution */ Datum h3_get_pentagons(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); int resolution = PG_GETARG_INT32(0); int count = pentagonCount(); H3Index *indexes = palloc(count * sizeof(H3Index)); h3_assert(getPentagons(resolution, indexes)); funcctx->user_fctx = indexes; funcctx->max_calls = count; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* The great circle distance in radians between two spherical coordinates */ Datum h3_great_circle_distance(PG_FUNCTION_ARGS) { Point *aPoint = PG_GETARG_POINT_P(0); Point *bPoint = PG_GETARG_POINT_P(1); char *unit = text_to_cstring(PG_GETARG_TEXT_PP(2)); LatLng a; LatLng b; double distance; a.lng = degsToRads(aPoint->x); a.lat = degsToRads(aPoint->y); b.lng = degsToRads(bPoint->x); b.lat = degsToRads(bPoint->y); if (strcmp(unit, "rads") == 0) distance = greatCircleDistanceRads(&a, &b); else if (strcmp(unit, "km") == 0) distance = greatCircleDistanceKm(&a, &b); else if (strcmp(unit, "m") == 0) distance = greatCircleDistanceM(&a, &b); else ASSERT(0, ERRCODE_INVALID_PARAMETER_VALUE, "Unit must be m, km or rads."); PG_RETURN_FLOAT8(distance); } h3-pg-4.2.2/h3/src/binding/regions.c000066400000000000000000000255711475234715600170120ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // SRF_IS_FIRSTCALL #include // HeapTuple #include // ArrayType #include // PG_GETARG_POLYGON_P #include // get_typlenbyvalalign #include // POLYGONOID #include // text_to_cstring #include "error.h" #include "type.h" #include "srf.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_polygon_to_cells); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_polygon_to_cells_experimental); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cells_to_multi_polygon); static void polygonToGeoLoop(POLYGON *polygon, GeoLoop * geoloop) { geoloop->numVerts = polygon->npts; geoloop->verts = (LatLng *) palloc(geoloop->numVerts * sizeof(LatLng)); for (int i = 0; i < geoloop->numVerts; i++) { geoloop->verts[i].lng = degsToRads(polygon->p[i].x); geoloop->verts[i].lat = degsToRads(polygon->p[i].y); } } static int linkedGeoLoopToNativePolygonSize(LinkedGeoLoop * linkedLoop) { int count = 0; LinkedLatLng *linkedCoord = linkedLoop->first; while (linkedCoord != NULL) { count++; linkedCoord = linkedCoord->next; } return count; } static void linkedGeoLoopToNativePolygon(LinkedGeoLoop * linkedLoop, POLYGON *polygon) { int count; LinkedLatLng *linkedCoord = linkedLoop->first; count = 0; while (linkedCoord != NULL) { (polygon->p[count]).x = radsToDegs(linkedCoord->vertex.lng); (polygon->p[count]).y = radsToDegs(linkedCoord->vertex.lat); linkedCoord = linkedCoord->next; count++; } } /* * H3Error polygonToCells(const GeoPolygon *geoPolygon, int res, uint32_t flags, H3Index *out); */ Datum h3_polygon_to_cells(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); int64_t maxSize; H3Index *indices; ArrayType *holes; int nelems = 0; int resolution; GeoPolygon polygon; Datum value; bool isnull; POLYGON *exterior; if (PG_ARGISNULL(0)) ASSERT(0, ERRCODE_INVALID_PARAMETER_VALUE, "No polygon given to polyfill"); /* get function arguments */ exterior = PG_GETARG_POLYGON_P(0); if (!PG_ARGISNULL(1)) { holes = PG_GETARG_ARRAYTYPE_P(1); nelems = ArrayGetNItems(ARR_NDIM(holes), ARR_DIMS(holes)); } resolution = PG_GETARG_INT32(2); /* build polygon */ polygonToGeoLoop(exterior, &(polygon.geoloop)); if (nelems) { int i = 0; ArrayIterator iterator = array_create_iterator(holes, 0, NULL); polygon.numHoles = nelems; polygon.holes = (GeoLoop *) palloc(polygon.numHoles * sizeof(GeoLoop)); while (array_iterate(iterator, &value, &isnull)) { if (isnull) { polygon.numHoles--; } else { POLYGON *hole = DatumGetPolygonP(value); polygonToGeoLoop(hole, &(polygon.holes[i])); i++; } } } else { polygon.numHoles = 0; } /* produce hexagons into allocated memory */ h3_assert(maxPolygonToCellsSize(&polygon, resolution, 0, &maxSize)); indices = palloc_extended(maxSize * sizeof(H3Index), MCXT_ALLOC_HUGE | MCXT_ALLOC_ZERO); h3_assert(polygonToCells(&polygon, resolution, 0, indices)); funcctx->user_fctx = indices; funcctx->max_calls = maxSize; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* * H3Error polygonToCells(const GeoPolygon *geoPolygon, int res, uint32_t flags, H3Index *out); */ Datum h3_polygon_to_cells_experimental(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); char *containment_mode; int64_t maxSize; H3Index *indices; ArrayType *holes; int nelems = 0; uint32_t flags = 0; int resolution; GeoPolygon polygon; Datum value; bool isnull; POLYGON *exterior; if (PG_ARGISNULL(0)) ASSERT(0, ERRCODE_INVALID_PARAMETER_VALUE, "No polygon given to polyfill"); /* get function arguments */ exterior = PG_GETARG_POLYGON_P(0); if (!PG_ARGISNULL(1)) { holes = PG_GETARG_ARRAYTYPE_P(1); nelems = ArrayGetNItems(ARR_NDIM(holes), ARR_DIMS(holes)); } resolution = PG_GETARG_INT32(2); if (!PG_ARGISNULL(3)) { containment_mode = text_to_cstring(PG_GETARG_TEXT_PP(3)); if (strcmp(containment_mode, "center") == 0) flags = 0; else if (strcmp(containment_mode, "full") == 0) flags = 1; else if (strcmp(containment_mode, "overlapping") == 0) flags = 2; else if (strcmp(containment_mode, "overlapping_bbox") == 0) flags = 3; else ASSERT(0, ERRCODE_INVALID_PARAMETER_VALUE, "Containment Mode must be center, full, overlapping, or overlapping_bbox."); } /* build polygon */ polygonToGeoLoop(exterior, &(polygon.geoloop)); if (nelems) { int i = 0; ArrayIterator iterator = array_create_iterator(holes, 0, NULL); polygon.numHoles = nelems; polygon.holes = (GeoLoop *) palloc(polygon.numHoles * sizeof(GeoLoop)); while (array_iterate(iterator, &value, &isnull)) { if (isnull) { polygon.numHoles--; } else { POLYGON *hole = DatumGetPolygonP(value); polygonToGeoLoop(hole, &(polygon.holes[i])); i++; } } } else { polygon.numHoles = 0; } /* produce hexagons into allocated memory */ h3_assert(maxPolygonToCellsSizeExperimental(&polygon, resolution, flags, &maxSize)); indices = palloc_extended(maxSize * sizeof(H3Index), MCXT_ALLOC_HUGE | MCXT_ALLOC_ZERO); h3_assert(polygonToCellsExperimental(&polygon, resolution, flags, maxSize, indices)); funcctx->user_fctx = indices; funcctx->max_calls = maxSize; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* * https://stackoverflow.com/questions/51127189/how-to-return-array-into-array-with-custom-type-in-postgres-c-function */ Datum h3_cells_to_multi_polygon(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; TupleDesc tuple_desc; LinkedGeoPolygon *linkedPolygon; LinkedGeoLoop *linkedLoop; if (SRF_IS_FIRSTCALL()) { Datum value; bool isnull; int i = 0; FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); int numHexes = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); ArrayIterator iterator = array_create_iterator(array, 0, NULL); H3Index *h3set = palloc(numHexes * sizeof(H3Index)); /* Extract data from array into h3set, and wipe compactedSet memory */ while (array_iterate(iterator, &value, &isnull)) { h3set[i++] = DatumGetH3Index(value); } /* produce hexagons into allocated memory */ linkedPolygon = palloc(sizeof(LinkedGeoPolygon)); h3_assert(cellsToLinkedMultiPolygon(h3set, numHexes, linkedPolygon)); ENSURE_TYPEFUNC_COMPOSITE(get_call_result_type(fcinfo, NULL, &tuple_desc)); funcctx->user_fctx = linkedPolygon; funcctx->tuple_desc = BlessTupleDesc(tuple_desc); MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); linkedPolygon = (LinkedGeoPolygon *) funcctx->user_fctx; if (linkedPolygon) { HeapTuple tuple; Datum result; int count; int size; POLYGON *polygon; Datum *elems; Datum values[2]; bool nulls[2]; int16 typlen; bool typbyval; char typalign; ArrayType *retarr; tuple_desc = funcctx->tuple_desc; linkedLoop = linkedPolygon->first; count = linkedGeoLoopToNativePolygonSize(linkedLoop); size = offsetof(POLYGON, p) +sizeof(polygon->p[0]) * count; polygon = palloc0(size); SET_VARSIZE(polygon, size); polygon->npts = count; linkedGeoLoopToNativePolygon(linkedLoop, polygon); values[0] = PolygonPGetDatum(polygon); nulls[0] = false; /* construct array */ count = 0; linkedLoop = linkedPolygon->first->next; while (linkedLoop != NULL) { count++; linkedLoop = linkedLoop->next; } elems = (Datum *) palloc(count * sizeof(Datum)); if (count) { linkedLoop = linkedPolygon->first->next; for (int i = 0; i < count; i++) { int subcount = linkedGeoLoopToNativePolygonSize(linkedLoop); POLYGON *polygon; int size = offsetof(POLYGON, p) +sizeof(polygon->p[0]) * subcount; polygon = palloc0(size); SET_VARSIZE(polygon, size); polygon->npts = subcount; linkedGeoLoopToNativePolygon(linkedLoop, polygon); elems[i] = PolygonPGetDatum(polygon); linkedLoop = linkedLoop->next; } } get_typlenbyvalalign(POLYGONOID, &typlen, &typbyval, &typalign); retarr = construct_array(elems, count, POLYGONOID, typlen, typbyval, typalign); values[1] = PointerGetDatum(retarr); nulls[1] = false; tuple = heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); funcctx->user_fctx = linkedPolygon->next; SRF_RETURN_NEXT(funcctx, result); } else { destroyLinkedMultiPolygon(linkedPolygon); SRF_RETURN_DONE(funcctx); } } /* --------------------------------------------------------------------------- * The GeoPolygon, LinkedLatLng, LinkedLatLng, * LinkedGeoLoop, and LinkedGeoPolygon * * copied from H3 core for reference */ /** @struct GeoPolygon * @brief Simplified core of GeoJSON Polygon coordinates definition */ /* typedef struct { double lat; ///< latitude in radians double lon; ///< longitude in radians } LatLng; typedef struct { int numVerts; LatLng *verts; } GeoLoop; 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 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; }; */ h3-pg-4.2.2/h3/src/binding/traversal.c000066400000000000000000000157421475234715600173460ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // SRF_IS_FIRSTCALL #include // PG_GETARG_POINT_P #include "error.h" #include "type.h" #include "srf.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_grid_disk); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_grid_disk_distances); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_grid_ring_unsafe); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_grid_distance); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_grid_path_cells); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_local_ij); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_local_ij_to_cell); /* * k-rings produces indices within k distance of the origin index. * * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and * all neighboring indices, and so on. * * Output is placed in the provided array in no particular order. * There may be fewer elements in output, as can happen when crossing a * pentagon. */ Datum h3_grid_disk(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); int64_t max; H3Index *indices; /* get function arguments */ H3Index origin = PG_GETARG_H3INDEX(0); int k = PG_GETARG_INT32(1); h3_assert(maxGridDiskSize(k, &max)); indices = palloc(max * sizeof(H3Index)); h3_assert(gridDisk(origin, k, indices)); funcctx->user_fctx = indices; funcctx->max_calls = max; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* * k-rings produces indices within k distance of the origin index. * * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and * all neighboring indices, and so on. * * Output is placed in the provided array in no particular order. * There may be fewer elements in output, as can happen when crossing a * pentagon. */ Datum h3_grid_disk_distances(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); TupleDesc tuple_desc; /* get function arguments */ H3Index origin = PG_GETARG_H3INDEX(0); int k = PG_GETARG_INT32(1); /* * Allocate memory for the indices, the distances and the tuple used * for */ /* returning */ int64_t maxSize; hexDistanceTuple *user_fctx; h3_assert(maxGridDiskSize(k, &maxSize)); user_fctx = palloc(sizeof(hexDistanceTuple)); user_fctx->indices = palloc(maxSize * sizeof(H3Index)); user_fctx->distances = palloc(maxSize * sizeof(int)); h3_assert(gridDiskDistances(origin, k, user_fctx->indices, user_fctx->distances)); ENSURE_TYPEFUNC_COMPOSITE(get_call_result_type(fcinfo, NULL, &tuple_desc)); funcctx->tuple_desc = BlessTupleDesc(tuple_desc); funcctx->max_calls = maxSize; funcctx->user_fctx = user_fctx; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEX_DISTANCES_FROM_USER_FCTX(); } /* * Produces the hollow hexagonal ring centered at origin with sides of length k. * * Throws if pentagonal distortion was encountered. */ Datum h3_grid_ring_unsafe(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* get function arguments */ H3Index *indices; H3Index origin = PG_GETARG_H3INDEX(0); int k = PG_GETARG_INT32(1); /* * Find the size of the ring. If k is 0, then it is the same as * k_ring. * * If k is larger than 0, the ring is the size of the circle with k, * minus the circle with k-1 */ int64_t maxSize; int64_t innerSize; h3_assert(maxGridDiskSize(k, &maxSize)); if (k > 0) { h3_assert(maxGridDiskSize(k - 1, &innerSize)); maxSize -= innerSize; } indices = palloc(maxSize * sizeof(H3Index)); h3_assert(gridRingUnsafe(origin, k, indices)); funcctx->user_fctx = indices; funcctx->max_calls = maxSize; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* * Returns the distance in grid cells between the two indexes. * * Returns a negative number if finding the distance failed. * Finding the distance can fail because the two indexes are not comparable * (different resolutions), too far apart, or are separated by pentagonal * distortion. This is the same set of limitations as the local IJ coordinate * space functions. */ Datum h3_grid_distance(PG_FUNCTION_ARGS) { H3Index originIndex = PG_GETARG_H3INDEX(0); H3Index h3Index = PG_GETARG_H3INDEX(1); int64_t distance; h3_assert(gridDistance(originIndex, h3Index, &distance)); PG_RETURN_INT64(distance); } /* * 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. */ Datum h3_grid_path_cells(PG_FUNCTION_ARGS) { if (SRF_IS_FIRSTCALL()) { FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* get function arguments */ int64_t size; H3Index *indices; H3Index start = PG_GETARG_H3INDEX(0); H3Index end = PG_GETARG_H3INDEX(1); h3_assert(gridPathCellsSize(start, end, &size)); indices = palloc(size * sizeof(H3Index)); h3_assert(gridPathCells(start, end, indices)); funcctx->user_fctx = indices; funcctx->max_calls = size; MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* * Produces local IJ coordinates for an H3 index anchored by an origin. */ Datum h3_cell_to_local_ij(PG_FUNCTION_ARGS) { H3Index origin = PG_GETARG_H3INDEX(0); H3Index index = PG_GETARG_H3INDEX(1); Point *point = (Point *) palloc(sizeof(Point)); CoordIJ coord; h3_assert(cellToLocalIj(origin, index, 0, &coord)); point->x = coord.i; point->y = coord.j; PG_RETURN_POINT_P(point); } /* * Produces an H3 index from local IJ coordinates anchored by an origin. */ Datum h3_local_ij_to_cell(PG_FUNCTION_ARGS) { H3Index origin = PG_GETARG_H3INDEX(0); Point *point = PG_GETARG_POINT_P(1); H3Index *index = (H3Index *) palloc(sizeof(H3Index)); CoordIJ coord; coord.i = point->x; coord.j = point->y; h3_assert(localIjToCell(origin, &coord, 0, index)); PG_FREE_IF_COPY(point, 1); PG_RETURN_H3INDEX(*index); } h3-pg-4.2.2/h3/src/binding/vertex.c000066400000000000000000000053421475234715600166530ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // SRF_IS_FIRSTCALL #include // PG_RETURN_POINT_P #include "error.h" #include "type.h" #include "srf.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_vertex); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_vertexes); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_vertex_to_lat_lng); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_is_valid_vertex); /* Returns a single vertex for a given cell, as an H3 index */ Datum h3_cell_to_vertex(PG_FUNCTION_ARGS) { H3Index vertex; H3Index cell = PG_GETARG_H3INDEX(0); int vertexNum = PG_GETARG_INT32(1); h3_assert(cellToVertex(cell, vertexNum, &vertex)); PG_RETURN_H3INDEX(vertex); } /* Returns all vertexes for a given cell, as H3 indexes */ Datum h3_cell_to_vertexes(PG_FUNCTION_ARGS) { /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { int maxSize; int size; H3Index *vertexes; /* create a function context for cross-call persistence */ FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); /* switch to memory context appropriate for multiple function calls */ MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* BEGIN One-time setup code */ H3Index cell = PG_GETARG_H3INDEX(0); maxSize = 6; size = maxSize * sizeof(H3Index); vertexes = palloc(size); h3_assert(cellToVertexes(cell, vertexes)); funcctx->user_fctx = vertexes; funcctx->max_calls = maxSize; /* END One-time setup code */ MemoryContextSwitchTo(oldcontext); } SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); } /* Get the geocoordinates of an H3 vertex */ Datum h3_vertex_to_lat_lng(PG_FUNCTION_ARGS) { H3Index vertex = PG_GETARG_H3INDEX(0); Point *point = palloc(sizeof(Point)); LatLng latlng; h3_assert(vertexToLatLng(vertex, &latlng)); point->x = radsToDegs(latlng.lng); point->y = radsToDegs(latlng.lat); PG_RETURN_POINT_P(point); } /* Whether the input is a valid H3 vertex */ Datum h3_is_valid_vertex(PG_FUNCTION_ARGS) { H3Index vertex = PG_GETARG_H3INDEX(0); bool is_valid = isValidVertex(vertex); PG_RETURN_BOOL(is_valid); } h3-pg-4.2.2/h3/src/config.h.in000066400000000000000000000013161475234715600156000ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #ifndef H3_CONFIG_H #define H3_CONFIG_H #define POSTGRESQL_H3_VERSION "@INSTALL_VERSION@" #endif /* H3_CONFIG_H */ h3-pg-4.2.2/h3/src/deprecated.c000066400000000000000000000064041475234715600160240ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include "deprecate.h" H3_DEPRECATE("1.0.0", h3_basecells); H3_DEPRECATE("1.0.0", h3_h3_get_base_cell); H3_DEPRECATE("1.0.0", h3_h3_get_resolution); H3_DEPRECATE("1.0.0", h3_h3_indexes_are_neighbors); H3_DEPRECATE("1.0.0", h3_h3_is_pentagon); H3_DEPRECATE("1.0.0", h3_h3_is_res_class_iii); H3_DEPRECATE("1.0.0", h3_h3_is_valid); H3_DEPRECATE("1.0.0", h3_h3_set_to_linked_geo); H3_DEPRECATE("1.0.0", h3_h3_to_children); H3_DEPRECATE("1.0.0", h3_h3_to_geo_boundary); H3_DEPRECATE("1.0.0", h3_h3_to_geo); H3_DEPRECATE("1.0.0", h3_h3_to_parent); H3_DEPRECATE("1.0.0", h3_h3_unidirectional_edge_is_valid); H3_DEPRECATE("1.0.0", h3_haversine_distance); H3_DEPRECATE("3.4.0", h3_degs_to_rads); H3_DEPRECATE("3.4.0", h3_rads_to_degs); H3_DEPRECATE("3.5.0", h3_edge_length_km); H3_DEPRECATE("3.5.0", h3_edge_length_m); H3_DEPRECATE("3.5.0", h3_get_unidirectional_edge_boundary); H3_DEPRECATE("3.5.0", h3_hex_area_km2); H3_DEPRECATE("3.5.0", h3_hex_area_m2); H3_DEPRECATE("3.5.0", h3_hex_range_distances); H3_DEPRECATE("3.5.0", h3_hex_range); H3_DEPRECATE("3.5.0", h3_hex_ranges); H3_DEPRECATE("3.6.0", h3_h3_to_string); H3_DEPRECATE("3.6.0", h3_string_to_h3); H3_DEPRECATE("4.0.0", h3_compact); H3_DEPRECATE("4.0.0", h3_distance); H3_DEPRECATE("4.0.0", h3_exact_edge_length); H3_DEPRECATE("4.0.0", h3_experimental_h3_to_local_ij); H3_DEPRECATE("4.0.0", h3_experimental_local_ij_to_h3); H3_DEPRECATE("4.0.0", h3_geo_to_h3); H3_DEPRECATE("4.0.0", h3_get_base_cell); H3_DEPRECATE("4.0.0", h3_get_destination_h3_index_from_unidirectional_edge); H3_DEPRECATE("4.0.0", h3_get_faces); H3_DEPRECATE("4.0.0", h3_get_h3_indexes_from_unidirectional_edge); H3_DEPRECATE("4.0.0", h3_get_h3_unidirectional_edge_boundary); H3_DEPRECATE("4.0.0", h3_get_h3_unidirectional_edge); H3_DEPRECATE("4.0.0", h3_get_h3_unidirectional_edges_from_hexagon); H3_DEPRECATE("4.0.0", h3_get_origin_h3_index_from_unidirectional_edge); H3_DEPRECATE("4.0.0", h3_get_pentagon_indexes); H3_DEPRECATE("4.0.0", h3_get_res_0_indexes); H3_DEPRECATE("4.0.0", h3_hex_area); H3_DEPRECATE("4.0.0", h3_hex_ring); H3_DEPRECATE("4.0.0", h3_indexes_are_neighbors); H3_DEPRECATE("4.0.0", h3_is_valid); H3_DEPRECATE("4.0.0", h3_k_ring_distances); H3_DEPRECATE("4.0.0", h3_k_ring); H3_DEPRECATE("4.0.0", h3_line); H3_DEPRECATE("4.0.0", h3_num_hexagons); H3_DEPRECATE("4.0.0", h3_point_dist); H3_DEPRECATE("4.0.0", h3_polyfill); H3_DEPRECATE("4.0.0", h3_set_to_multi_polygon); H3_DEPRECATE("4.0.0", h3_to_center_child); H3_DEPRECATE("4.0.0", h3_to_geo_boundary); H3_DEPRECATE("4.0.0", h3_to_geo); H3_DEPRECATE("4.0.0", h3_uncompact); H3_DEPRECATE("4.0.0", h3_unidirectional_edge_is_valid); H3_DEPRECATE("4.1.0", h3_cell_to_boundary_wkb); H3_DEPRECATE("4.1.0", h3_cells_to_multi_polygon_wkb); h3-pg-4.2.2/h3/src/extension.c000066400000000000000000000024761475234715600157450ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_INFO_V1 #include // cstring_to_text #include "type.h" #include "config.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_get_extension_version); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_pg_migrate_pass_by_reference); /* Return version number for this extension (not main h3 lib) */ Datum h3_get_extension_version(PG_FUNCTION_ARGS) { PG_RETURN_TEXT_P(cstring_to_text(POSTGRESQL_H3_VERSION)); } /* * Migration from pass-by-reference to pass-by-value * https://github.com/zachasme/h3-pg/issues/31 */ Datum h3_pg_migrate_pass_by_reference(PG_FUNCTION_ARGS) { H3Index cell = (*((H3Index *) DatumGetPointer(PG_GETARG_DATUM(0)))); PG_RETURN_H3INDEX(cell); } h3-pg-4.2.2/h3/src/guc.c000066400000000000000000000023461475234715600145030ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #include #include // DefineCustom*Variable bool h3_guc_strict = false; bool h3_guc_extend_antimeridian = false; void _guc_init(void) { DefineCustomBoolVariable("h3.strict", "Enable strict indexing (fail on invalid lng/lat).", NULL, &h3_guc_strict, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("h3.extend_antimeridian", "Extend boundaries by 180th meridian, when possible.", NULL, &h3_guc_extend_antimeridian, false, PGC_USERSET, 0, NULL, NULL, NULL); } h3-pg-4.2.2/h3/src/guc.h000066400000000000000000000013551475234715600145070ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #ifndef H3_GUC_H #define H3_GUC_H extern bool h3_guc_strict; extern bool h3_guc_extend_antimeridian; void _guc_init(void); #endif /* H3_GUC_H */ h3-pg-4.2.2/h3/src/init.c000066400000000000000000000017071475234715600146700ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ /* required for MSVC */ #define _USE_MATH_DEFINES #include #include // PG_MODULE_MAGIC #include "guc.h" /* see https://www.postgresql.org/docs/current/xfunc-c.html#XFUNC-C-DYNLOAD */ PG_MODULE_MAGIC; void _PG_init(void) { /* In case we allow using shared library h3, */ /* we could make version number assertion here */ _guc_init(); } h3-pg-4.2.2/h3/src/opclass_btree.c000066400000000000000000000041331475234715600165460ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // SortSupport #include "type.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_cmp); Datum h3index_cmp(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); uint32_t ret = 0; if (a < b) ret = 1; else if (a > b) ret = -1; PG_RETURN_INT32(ret); } static int h3index_cmp_abbrev(Datum x, Datum y, SortSupport ssup) { if (x == y) return 0; else if (x < y) return 1; else return -1; } static int h3index_cmp_full(Datum x, Datum y, SortSupport ssup) { H3Index a = DatumGetH3Index(x); H3Index b = DatumGetH3Index(y); if (a == b) return 0; else if (a < b) return 1; return -1; } static bool h3index_abbrev_abort(int memtupcount, SortSupport ssup) { return 0; } static Datum h3index_abbrev_convert(Datum original, SortSupport ssup) { H3Index a = DatumGetH3Index(original); return a; } /* * Sort support strategy routine */ PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_sortsupport); Datum h3index_sortsupport(PG_FUNCTION_ARGS) { SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); ssup->comparator = h3index_cmp_full; ssup->ssup_extra = NULL; /* Enable sortsupport only on 64 bit Datum */ if (ssup->abbreviate && sizeof(Datum) == 8) { ssup->comparator = h3index_cmp_abbrev; ssup->abbrev_converter = h3index_abbrev_convert; ssup->abbrev_abort = h3index_abbrev_abort; ssup->abbrev_full_comparator = h3index_cmp_full; } PG_RETURN_VOID(); } h3-pg-4.2.2/h3/src/opclass_hash.c000066400000000000000000000023421475234715600163700ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // hash_any #include "type.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_hash); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_hash_extended); Datum h3index_hash(PG_FUNCTION_ARGS) { H3Index index = PG_GETARG_H3INDEX(0); Datum hash = hash_any((unsigned char *) &index, sizeof(index)); PG_RETURN_DATUM(hash); } Datum h3index_hash_extended(PG_FUNCTION_ARGS) { H3Index index = PG_GETARG_H3INDEX(0); int64_t seed = PG_GETARG_INT64(1); Datum hash = hash_any_extended((unsigned char *) &index, sizeof(index), seed); PG_RETURN_DATUM(hash); } h3-pg-4.2.2/h3/src/opclass_spgist.c000066400000000000000000000306661475234715600167700ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include // Datum, etc. #include // PG_FUNCTION_ARGS, etc. #include // SP-GiST #include "catalog/pg_type.h" #include // Main H3 include #include "type.h" #include "error.h" #include "inttypes.h" #include "upstream_macros.h" // Technically not public API, but we need the bit macros PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_spgist_config); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_spgist_choose); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_spgist_picksplit); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_spgist_inner_consistent); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_spgist_leaf_consistent); #define H3_ROOT_INDEX -1 #define NUM_BASE_CELLS 122 #define MAX_H3_RES 15 #define H3_NUM_CHILDREN 7 static int spgist_cmp(H3Index * a, H3Index * b) { int aRes = getResolution(*a); int bRes = getResolution(*b); H3Index aParent; H3Index bParent; H3Error error; cellToParent(*a, bRes, &aParent); cellToParent(*b, aRes, &bParent); /* a contains b */ if (*a == H3_ROOT_INDEX || *a == bParent) { return 1; } /* a contained by b */ if (*b == H3_ROOT_INDEX || *b == aParent) { return -1; } /* no overlap */ return 0; } /* * Returns static information about the index implementation, including the data * type OIDs of the prefix and node label data types. * * The first argument is a pointer to a spgConfigIn C struct, containing input * data for the function. * * The second argument is a pointer to a spgConfigOut C struct, which the * function must fill with result data. */ Datum h3index_spgist_config(PG_FUNCTION_ARGS) { // struct { // Oid attType data type to be indexed // } spgConfigIn *in = (spgConfigIn *) PG_GETARG_POINTER(0); // struct { // Oid prefixType data type of inner-tuple prefixes // Oid labelType data type of inner-tuple node labels // Oid leafType data type of leaf-tuple values // bool canReturnData opclass can reconstruct original data // bool longValuesOK opclass can cope with values > 1 page // } spgConfigOut *out = (spgConfigOut *) PG_GETARG_POINTER(1); /* prefix is parent H3 index */ out->prefixType = in->attType; /* no need for labels */ out->labelType = VOIDOID; /* * leafType should match the index storage type defined by the operator * class's opckeytype catalog entry. (Note that opckeytype can be zero, * implying the storage type is the same as the operator class's input type, * which is the most common situation.) For reasons of backward compatibility, * the config method can set leafType to some other value, and that value will * be used; but this is deprecated since the index contents are then * incorrectly identified in the catalogs. Also, it's permissible to leave * leafType uninitialized (zero); that is interpreted as meaning the index * storage type derived from opckeytype. */ //out->leafType = 0; /* * canReturnData should be set true if the operator class is capable of * reconstructing the originally-supplied index value */ out->canReturnData = true; out->longValuesOK = false; PG_RETURN_VOID(); } /* * h3index_spgist_choose * Chooses a method for inserting a new value into an inner tuple * * The choose function can determine either that the new value matches one of * the existing child nodes, or that a new child node must be added, or that the * new value is inconsistent with the tuple prefix and so the inner tuple must * be split to create a less restrictive prefix. * * NOTE: When working with an inner tuple having unlabeled nodes, it is an error * for choose to return spgAddNode, since the set of nodes is supposed to be * fixed in such cases. Also, there is no provision for generating an unlabeled * node in spgSplitTuple actions, since it is expected that an spgAddNode action * will be needed as well. */ Datum h3index_spgist_choose(PG_FUNCTION_ARGS) { /*-------------------------------------------------------------------------- * Datum datum original datum to be indexed * Datum leafDatum current datum to be stored at leaf * int level current level (counting from zero) * * (data from current inner tuple) * bool allTheSame tuple is marked all-the-same? * bool hasPrefix tuple has a prefix? * Datum prefixDatum if so, the prefix value * int nNodes number of nodes in the inner tuple * Datum* nodeLabels node label values (NULL if none) *-------------------------------------------------------------------------- */ spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0); /*-------------------------------------------------------------------------- * spgChooseResultType resultType action code, see below * * -- results for spgMatchNode -- * int nodeN descend to this node (index from 0) * int levelAdd increment level by this much * Datum restDatum new leaf datum * * -- results for spgAddNode -- * Datum nodeLabel new node's label * int nodeN where to insert it (index from 0) * * -- results for spgSplitTuple -- * (info to form new upper-level inner tuple with one child tuple) * bool prefixHasPrefix tuple should have a prefix? * Datum prefixPrefixDatum if so, its value * int prefixNNodes number of nodes * Datum* prefixNodeLabels their labels (or NULL for no labels) * int childNodeN which node gets child tuple * (info to form new lower-level inner tuple with all old nodes) * bool postfixHasPrefix tuple should have a prefix? * Datum postfixPrefixDatum if so, its value *-------------------------------------------------------------------------- */ spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1); int resolution = in->level; H3Index insert = DatumGetH3Index(in->datum); int node; out->resultType = spgMatchNode; out->result.matchNode.levelAdd = 1; out->result.matchNode.restDatum = H3IndexGetDatum(insert); if (!in->allTheSame) { if (resolution == 0) { node = getBaseCellNumber(insert); } else { node = H3_GET_INDEX_DIGIT(insert, resolution); } out->result.matchNode.nodeN = node; } PG_RETURN_VOID(); } /** * Decides how to create a new inner tuple over a set of leaf tuples. * * An inner tuple contains a set of one or more nodes, * which represent groups of similar leaf values. * * struct spgPickSplitIn * int nTuples number of leaf tuples * Datum *datums their datums (array of length nTuples) * int level current level (counting from zero) * * struct spgPickSplitOut * bool hasPrefix new inner tuple should have a prefix? * Datum prefixDatum if so, its value * int nNodes number of nodes for new inner tuple * Datum *nodeLabels their labels (or NULL for no labels) * int *mapTuplesToNodes node index for each leaf tuple * Datum *leafTupleDatums datum to store in each new leaf tuple */ Datum h3index_spgist_picksplit(PG_FUNCTION_ARGS) { spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0); spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1); int resolution = in->level; /* we don't need node labels */ out->nodeLabels = NULL; out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples); out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples); if (resolution == 0) { /* at resolution 0 there is one node per base cell */ out->nNodes = NUM_BASE_CELLS; out->hasPrefix = false; } else { /* at finer resolutions there is exactly 7 nodes, one per child */ H3Index first = DatumGetH3Index(in->datums[0]); H3Index parent; H3Error error; h3_assert(cellToParent(first, resolution, &parent)); /* * TODO: consider decreasing nNodes for pentagons which only have 6 * children? */ out->nNodes = H3_NUM_CHILDREN; out->hasPrefix = true; out->prefixDatum = H3IndexGetDatum(parent); } /* map each leaf tuple to node in the new inner tuple */ for (int i = 0; i < in->nTuples; i++) { H3Index insert = DatumGetH3Index(in->datums[i]); int node; if (resolution == 0) { /* first resolution is base cells */ node = getBaseCellNumber(insert); } else if (getResolution(insert) >= resolution) { /* finer resolutions use index digit 0-6 */ node = H3_GET_INDEX_DIGIT(insert, resolution); } else { /* coarse indexes are put into center node */ node = 0; } out->leafTupleDatums[i] = H3IndexGetDatum(insert); out->mapTuplesToNodes[i] = node; } PG_RETURN_VOID(); } /** * Returns set of nodes (branches) to follow during tree search. * * Each query is a single H3 index to be checked against the parent prefix * * We either return all or none (except for res 0) */ Datum h3index_spgist_inner_consistent(PG_FUNCTION_ARGS) { spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); H3Index parent = H3_NULL; int bc, i; bool stop; int innerNodes = in->nNodes; if (in->hasPrefix) { parent = DatumGetH3Index(in->prefixDatum); } if (in->allTheSame) { /* Report that all nodes should be visited */ out->nNodes = in->nNodes; out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); for (i = 0; i < in->nNodes; i++) { out->nodeNumbers[i] = i; } PG_RETURN_VOID(); } out->levelAdds = palloc(sizeof(int) * innerNodes); for (i = 0; i < innerNodes; ++i) out->levelAdds[i] = 1; /* We must descend into the quadrant(s) identified by which */ out->nodeNumbers = (int *) palloc(sizeof(int) * innerNodes); out->nNodes = 0; /* "which" is a bitmask of child nodes that satisfy all constraints */ bc = -1; stop = false; for (i = 0; i < in->nkeys; i++) { /* each scankey is a constraint to be checked against */ StrategyNumber strategy = in->scankeys[i].sk_strategy; H3Index query = DatumGetH3Index(in->scankeys[i].sk_argument); if (parent == H3_NULL) { if (bc > -1) { stop = true; } bc = getBaseCellNumber(query); } else { switch (strategy) { case RTSameStrategyNumber: if (spgist_cmp(&parent, &query) == 0) stop = true; break; case RTContainsStrategyNumber: if (spgist_cmp(&parent, &query) == 0) stop = true; /* no overlap */ break; case RTContainedByStrategyNumber: if (spgist_cmp(&parent, &query) == 0) stop = true; /* no overlap */ break; default: elog(ERROR, "unrecognized strategy number: %d", strategy); break; } } if (stop) break; /* no need to consider remaining conditions */ } if (!stop) { if (bc > -1) { out->nodeNumbers[out->nNodes] = bc; out->nNodes++; } else { for (i = 0; i < innerNodes; i++) { out->nodeNumbers[out->nNodes] = i; out->nNodes++; } } } PG_RETURN_VOID(); } /** * Returns true if a leaf satisfies a query. * * To satisfy the query, the leaf must satisfy all the conditions described by scankeys. */ Datum h3index_spgist_leaf_consistent(PG_FUNCTION_ARGS) { spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0); spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1); H3Index leaf = DatumGetH3Index(in->leafDatum); bool retval = true; out->leafValue = in->leafDatum; /* leafDatum is what it is... */ out->recheck = false; /* all tests are exact */ /* Perform the required comparison(s) */ for (int i = 0; i < in->nkeys; i++) { StrategyNumber strategy = in->scankeys[i].sk_strategy; H3Index query = DatumGetH3Index(in->scankeys[i].sk_argument); switch (strategy) { case RTSameStrategyNumber: /* leaf is equal to query */ retval = (leaf == query); break; case RTContainsStrategyNumber: /* leaf contains the query */ retval = (spgist_cmp(&leaf, &query) > 0); break; case RTContainedByStrategyNumber: /* leaf is contained by the query */ retval = (spgist_cmp(&leaf, &query) < 0); break; default: ereport(ERROR, ( errcode(ERRCODE_INTERNAL_ERROR), errmsg("unrecognized StrategyNumber: %d", strategy)) ); } if (!retval) break; } PG_RETURN_BOOL(retval); } h3-pg-4.2.2/h3/src/operators.c000066400000000000000000000072221475234715600157410ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include "error.h" #include "type.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_distance); /* b-tree */ PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_eq); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_ne); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_lt); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_le); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gt); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_ge); /* r-tree */ PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_overlaps); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_contains); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_contained_by); /* static helpers */ static int containment(H3Index a, H3Index b) { H3Index aParent = a; H3Index bParent = b; int aRes = getResolution(a); int bRes = getResolution(b); if (aRes > bRes) h3_assert(cellToParent(a, bRes, &aParent)); else if (aRes < bRes) h3_assert(cellToParent(b, aRes, &bParent)); /* a contains b */ if (a == bParent) return 1; /* a contained by b */ if (b == aParent) return -1; /* no overlap */ return 0; } /* distance operator allowing for different resolutions */ Datum h3index_distance(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); int resA = getResolution(a); int resB = getResolution(b); H3Error error; int64_t distance; if (resA < resB) h3_assert(cellToCenterChild(a, resB, &a)); else if (resB < resA) h3_assert(cellToCenterChild(b, resA, &b)); error = gridDistance(a, b, &distance); /* h3_assert(error); */ if (error) distance = -1; PG_RETURN_INT64(distance); } /* b-tree operators */ Datum h3index_eq(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = a == b; PG_RETURN_BOOL(ret); } Datum h3index_ne(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = a != b; PG_RETURN_BOOL(ret); } Datum h3index_lt(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = a < b; PG_RETURN_BOOL(ret); } Datum h3index_le(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = a <= b; PG_RETURN_BOOL(ret); } Datum h3index_gt(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = a > b; PG_RETURN_BOOL(ret); } Datum h3index_ge(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = a >= b; PG_RETURN_BOOL(ret); } /* r-tree operators */ Datum h3index_overlaps(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = containment(a, b) != 0; PG_RETURN_BOOL(ret); } Datum h3index_contains(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = containment(a, b) > 0; PG_RETURN_BOOL(ret); } Datum h3index_contained_by(PG_FUNCTION_ARGS) { H3Index a = PG_GETARG_H3INDEX(0); H3Index b = PG_GETARG_H3INDEX(1); bool ret = containment(a, b) < 0; PG_RETURN_BOOL(ret); } h3-pg-4.2.2/h3/src/srf.c000066400000000000000000000045571475234715600145250ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #include #include #include // SRF_IS_FIRSTCALL #include // HeapTuple #include "type.h" #include "srf.h" /* * Set-Returning-Function assume user fctx contains indices * will skip missing (all zeros) indices */ Datum srf_return_h3_indexes_from_user_fctx(PG_FUNCTION_ARGS) { FuncCallContext *funcctx = SRF_PERCALL_SETUP(); int call_cntr = funcctx->call_cntr; int max_calls = funcctx->max_calls; H3Index *indices = (H3Index *) funcctx->user_fctx; /* skip missing indices (all zeros) */ while (call_cntr < max_calls && !indices[call_cntr]) { funcctx->call_cntr = ++call_cntr; }; if (call_cntr < max_calls) { Datum result = H3IndexGetDatum(indices[call_cntr]); SRF_RETURN_NEXT(funcctx, result); } else { SRF_RETURN_DONE(funcctx); } } /* * Returns hex/distance tuples from user_fctx * will skip missing (all zeros) indices */ Datum srf_return_h3_index_distances_from_user_fctx(PG_FUNCTION_ARGS) { FuncCallContext *funcctx = SRF_PERCALL_SETUP(); int call_cntr = funcctx->call_cntr; int max_calls = funcctx->max_calls; hexDistanceTuple *user_fctx = funcctx->user_fctx; H3Index *indices = user_fctx->indices; int *distances = user_fctx->distances; /* skip missing indices (all zeros) */ while (!indices[call_cntr]) { funcctx->call_cntr = ++call_cntr; }; if (call_cntr < max_calls) { TupleDesc tuple_desc = funcctx->tuple_desc; Datum values[2]; bool nulls[2] = {false}; HeapTuple tuple; Datum result; values[0] = H3IndexGetDatum(indices[call_cntr]); values[1] = Int32GetDatum(distances[call_cntr]); tuple = heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else { SRF_RETURN_DONE(funcctx); } } h3-pg-4.2.2/h3/src/srf.h000066400000000000000000000023001475234715600145120ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #ifndef H3_SRF_H #define H3_SRF_H #include typedef struct { H3Index *indices; int *distances; } hexDistanceTuple; /* helper functions to return sets from user fctx */ Datum srf_return_h3_indexes_from_user_fctx(PG_FUNCTION_ARGS); Datum srf_return_h3_index_distances_from_user_fctx(PG_FUNCTION_ARGS); /* macros to pass on fcinfo to above helpers */ #define SRF_RETURN_H3_INDEXES_FROM_USER_FCTX() \ return srf_return_h3_indexes_from_user_fctx(fcinfo) #define SRF_RETURN_H3_INDEX_DISTANCES_FROM_USER_FCTX() \ return srf_return_h3_index_distances_from_user_fctx(fcinfo) #endif /* H3_SRF_H */ h3-pg-4.2.2/h3/src/type.c000066400000000000000000000040301475234715600146760ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // needed for send/recv functions #include "error.h" #include "type.h" /* conversion */ PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_in); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_out); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_send); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_recv); PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_to_bigint); PGDLLEXPORT PG_FUNCTION_INFO_V1(bigint_to_h3index); /* textual input/output functions */ Datum h3index_in(PG_FUNCTION_ARGS) { char *string = PG_GETARG_CSTRING(0); H3Index h3; h3_assert(stringToH3(string, &h3)); PG_RETURN_H3INDEX(h3); } Datum h3index_out(PG_FUNCTION_ARGS) { H3Index h3 = PG_GETARG_H3INDEX(0); char *string = palloc(17 * sizeof(char)); h3_assert(h3ToString(h3, string, 17)); PG_RETURN_CSTRING(string); } Datum h3index_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); PG_RETURN_H3INDEX(pq_getmsgint64(buf)); } Datum h3index_send(PG_FUNCTION_ARGS) { H3Index h3 = PG_GETARG_H3INDEX(0); StringInfoData buf; pq_begintypsend(&buf); pq_sendint64(&buf, h3); PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } /* bigint conversion functions */ Datum h3index_to_bigint(PG_FUNCTION_ARGS) { H3Index h3index = PG_GETARG_H3INDEX(0); PG_RETURN_INT64(h3index); } Datum bigint_to_h3index(PG_FUNCTION_ARGS) { int64_t bigint = PG_GETARG_INT64(0); PG_RETURN_H3INDEX(bigint); } h3-pg-4.2.2/h3/src/upstream_macros.h000066400000000000000000000043061475234715600171340ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ /* Macros/etc copied from upsteam (needed for Debian packaging) */ #ifndef H3_UPSTREAM_MACROS_H #define H3_UPSTREAM_MACROS_H #include "h3api.h" /* SOURCE coordijk.h */ /** @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; /* SOURCE h3Index.h */ /** The number of bits in a single H3 resolution digit. */ #define H3_PER_DIGIT_OFFSET 3 /** 1's in the 3 bits of res 15 digit bits, 0's everywhere else. */ #define H3_DIGIT_MASK ((uint64_t)(7)) /** * 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))) #endif /* H3_UPSTREAM_MACROS_H */ h3-pg-4.2.2/h3/test/000077500000000000000000000000001475234715600137445ustar00rootroot00000000000000h3-pg-4.2.2/h3/test/CMakeLists.txt000066400000000000000000000016071475234715600165100ustar00rootroot00000000000000set(TESTS extension hierarchy indexing inspection miscellaneous opclass_brin opclass_btree opclass_hash opclass_spgist regions traversal type edge ) if(PostgreSQL_REGRESS) add_test( NAME h3_regress COMMAND ${PostgreSQL_REGRESS} --temp-instance=${CMAKE_BINARY_DIR}/tmp --bindir=${PostgreSQL_BIN_DIR} --inputdir=${CMAKE_CURRENT_SOURCE_DIR} --outputdir=${CMAKE_CURRENT_BINARY_DIR} --load-extension h3 ${TESTS} ) endif() if(PostgreSQL_VALIDATE_EXTUPGRADE) add_test( NAME h3_validate_extupgrade COMMAND pg_validate_extupgrade --extname h3 --from 0.1.0 --to ${INSTALL_VERSION} ) endif() # @TODO: Figure out how to inline on MacOS if(NOT APPLE) add_test( NAME h3_inlined COMMAND sh -c "! objdump -D ${PostgreSQL_PKG_LIBRARY_DIR}/h3.so | grep radsToDegs" CONFIGURATIONS Release ) endif() h3-pg-4.2.2/h3/test/expected/000077500000000000000000000000001475234715600155455ustar00rootroot00000000000000h3-pg-4.2.2/h3/test/expected/clustering.out000066400000000000000000000007471475234715600204650ustar00rootroot00000000000000\pset tuples_only on \set string '\'801dfffffffffff\'' \set hexagon ':string::h3index' CREATE TABLE h3_test_clustering (hex h3index PRIMARY KEY); INSERT INTO h3_test_clustering (hex) SELECT * from h3_get_res_0_cells(); CREATE INDEX h3_test_clustering_index ON h3_test_clustering USING btree (hex); CLUSTER h3_test_clustering_index ON h3_test_clustering; -- -- TEST clustering on B-tree -- SELECT hex = :hexagon FROM ( SELECT hex FROM h3_test_clustering WHERE hex = :hexagon ) q; t h3-pg-4.2.2/h3/test/expected/edge.out000066400000000000000000000025101475234715600172000ustar00rootroot00000000000000\pset tuples_only on \set hexagon '\'880326b885fffff\'::h3index' \set neighbor '\'880326b887fffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set edge '\'1180326b885fffff\'::h3index' -- -- TEST h3_are_neighbor_cells -- SELECT h3_are_neighbor_cells(:hexagon, :neighbor); t SELECT NOT h3_are_neighbor_cells(:hexagon, :hexagon); t -- -- TEST h3_cells_to_directed_edge -- SELECT h3_cells_to_directed_edge(:hexagon, :neighbor) = :edge; t -- -- TEST h3_is_valid_directed_edge -- SELECT h3_is_valid_directed_edge(:edge); t SELECT NOT h3_is_valid_directed_edge(:hexagon); t -- -- TEST h3_get_directed_edge_origin and -- h3_get_directed_edge_destination -- SELECT h3_get_directed_edge_origin(:edge) = :hexagon AND h3_get_directed_edge_destination(:edge) = :neighbor; t -- -- TEST h3_directed_edge_to_cells -- SELECT h3_directed_edge_to_cells(:edge) = (:hexagon, :neighbor); t -- -- TEST h3_origin_to_directed_edges -- SELECT array_length(array_agg(edge), 1) = 6 FROM ( SELECT h3_origin_to_directed_edges(:hexagon) edge ) q; t SELECT array_length(array_agg(edge), 1) = 5 expected FROM ( SELECT h3_origin_to_directed_edges(:pentagon) edge ) q; t -- -- TEST h3_directed_edge_to_boundary -- SELECT h3_directed_edge_to_boundary(:edge) ~= polygon '((89.5830164946548,64.7146398954916),(89.5790678021742,64.2872231517217))' t h3-pg-4.2.2/h3/test/expected/extension.out000066400000000000000000000005141475234715600203120ustar00rootroot00000000000000\pset tuples_only on -- -- TEST h3_get_extension_version -- SELECT h3_get_extension_version() ~ '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' OR h3_get_extension_version() = 'unreleased'; t h3-pg-4.2.2/h3/test/expected/hierarchy.out000066400000000000000000000061151475234715600202570ustar00rootroot00000000000000\pset tuples_only on -- neighbouring indexes (one hexagon, one pentagon) at resolution 3 \set hexagon '\'831c02fffffffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set resolution 3 -- -- TEST h3_cell_to_children, h3_cell_to_parent and h3_to_center_chil -- -- all parents of one the children of a hexagon, must be the original hexagon SELECT bool_and(i = :hexagon) FROM ( SELECT h3_cell_to_parent(h3_cell_to_children(:hexagon)) i ) q; t -- one child of the parent of a hexagon, must be the original hexagon SELECT bool_or(i = :hexagon) FROM ( SELECT h3_cell_to_children(h3_cell_to_parent(:hexagon)) i ) q; t -- all parents of one the children of a pentagon, must be the original pentagon SELECT bool_and(i = :pentagon) FROM ( SELECT h3_cell_to_parent(h3_cell_to_children(:pentagon)) i ) q; t -- one child of the parent of a pentagon, must be the original pentagon SELECT bool_or(i = :pentagon) FROM ( SELECT h3_cell_to_children(h3_cell_to_parent(:pentagon)) i ) q; t -- hexagon has 7 children SELECT array_length(array_agg(hex), 1) = 7 FROM ( SELECT h3_cell_to_children(:hexagon) hex ) q; t -- pentagon has 6 children SELECT array_length(array_agg(hex), 1) = 6 FROM ( SELECT h3_cell_to_children(:pentagon) hex ) q; t -- parent is one lower resolution SELECT h3_get_resolution(h3_cell_to_parent(:hexagon)) = :resolution -1; t -- all children is one higher resolution SELECT bool_and(r = :resolution +1) FROM ( SELECT h3_get_resolution(h3_cell_to_children(:hexagon)) r ) q; t -- parent of center child should be original index SELECT :hexagon = h3_cell_to_parent(h3_cell_to_center_child(:hexagon, 15), :resolution); t -- -- TEST h3_cell_to_child_pos and h3_child_pos_to_cell -- SELECT :hexagon = h3_child_pos_to_cell( h3_cell_to_child_pos(:hexagon, :resolution - 1), h3_cell_to_parent(:hexagon), :resolution ); t SELECT COUNT(*) = 7 FROM ( SELECT h3_cell_to_child_pos( h3_cell_to_children(:hexagon), :resolution - 1 ) ) q; t -- -- TEST h3_compact_cells and h3_uncompact_cells -- -- compacts the children of two hexes into the original two hexes SELECT array_agg(result) is null FROM ( SELECT h3_compact_cells( ARRAY(SELECT h3_cell_to_children(:hexagon) UNION SELECT h3_cell_to_children(:pentagon)) ) result EXCEPT SELECT unnest(ARRAY[:hexagon, :pentagon]) result ) q; t -- compact is inverse of uncompact SELECT h3_compact_cells(ARRAY(SELECT h3_uncompact_cells(ARRAY[:hexagon], :resolution))) = :hexagon; t -- uncompacts all to same resolution, gives same result as getting children SELECT array_agg(result) is null FROM ( SELECT h3_uncompact_cells(ARRAY( SELECT h3_cell_to_children(:hexagon) UNION SELECT :pentagon ), :resolution +2) result EXCEPT ( SELECT h3_cell_to_children(:hexagon, :resolution +2) result UNION SELECT h3_cell_to_children(:pentagon, :resolution +2) result ) ) q; t -- -- TEST h3_cell_to_children_slow -- -- h3_cell_to_children_slow and h3_cell_to_children have same result SELECT array_agg(result) is null FROM ( SELECT h3_cell_to_children_slow(:hexagon, :resolution + 3) result EXCEPT SELECT h3_cell_to_children(:hexagon, :resolution + 3) result ) q; t h3-pg-4.2.2/h3/test/expected/indexing.out000066400000000000000000000042311475234715600201030ustar00rootroot00000000000000\pset tuples_only on -- neighbouring indexes (one hexagon, one pentagon) at resolution 3 \set geo POINT(-144.52399108028, 49.7165031828995) \set hexagon '\'831c02fffffffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set edgecross '\'8003fffffffffff\'::h3index' \set resolution 3 -- -- TEST h3_cell_to_lat_lng and h3_lat_lng_to_cell -- -- convertion to geo works SELECT h3_cell_to_lat_lng(:hexagon) ~= :geo; t -- convertion to h3 index works SELECT h3_lat_lng_to_cell(:geo, :resolution) = :hexagon; t -- h3_cell_to_lat_lng is inverse of h3_lat_lng_to_cell SELECT h3_cell_to_lat_lng(i) ~= :geo AND h3_get_resolution(i) = :resolution FROM ( SELECT h3_lat_lng_to_cell(:geo, :resolution) AS i ) AS q; t -- h3_lat_lng_to_cell is inverse of h3_cell_to_lat_lng SELECT h3_lat_lng_to_cell(g, r) = :hexagon FROM ( SELECT h3_cell_to_lat_lng(:hexagon) AS g, h3_get_resolution(:hexagon) AS r ) AS q; t -- same for pentagon SELECT h3_lat_lng_to_cell(g, r) = :pentagon FROM ( SELECT h3_cell_to_lat_lng(:pentagon) AS g, h3_get_resolution(:pentagon) AS r ) AS q; t -- -- TEST h3_cell_to_boundary -- -- polyfill of geo boundary returns original index SELECT h3_polygon_to_cells(h3_cell_to_boundary(:hexagon), null, :resolution) = :hexagon; t -- same for pentagon SELECT h3_polygon_to_cells(h3_cell_to_boundary(:pentagon), null, :resolution) = :pentagon; t -- the boundary of an edgecrossing index is different with flag set to true SELECT h3_cell_to_boundary(:hexagon) ~= h3_cell_to_boundary(:hexagon, true) AND NOT h3_cell_to_boundary(:edgecross) ~= h3_cell_to_boundary(:edgecross, true); WARNING: Deprecated: Please use `SET h3.extend_antimeridian TO true` instead of extend flag WARNING: Deprecated: Please use `SET h3.extend_antimeridian TO true` instead of extend flag t -- cell to parent RES_MISMATCH CREATE FUNCTION h3_fail_indexing_cell_to_parent() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_cell_to_parent('831c02fffffffff', 10); RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; SELECT h3_fail_indexing_cell_to_parent(); t DROP FUNCTION h3_fail_indexing_cell_to_parent; h3-pg-4.2.2/h3/test/expected/inspection.out000066400000000000000000000024441475234715600204550ustar00rootroot00000000000000\pset tuples_only on -- neighbouring indexes (one hexagon, one pentagon) at resolution 3 \set invalid '\'0\'' \set hexagon '\'831c02fffffffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set resolution 3 -- -- TEST h3_get_resolution -- SELECT h3_get_resolution(:hexagon) = :resolution AND h3_get_resolution(:pentagon) = :resolution; t -- -- TEST h3_get_base_cell_number -- -- base cell is same for parents SELECT h3_get_base_cell_number(:hexagon) = h3_get_base_cell_number(h3_cell_to_parent(:hexagon)); t SELECT h3_get_base_cell_number(:pentagon) = h3_get_base_cell_number(h3_cell_to_parent(:pentagon)); t -- -- TEST h3_is_valid_cell -- SELECT h3_is_valid_cell(:hexagon) AND h3_is_valid_cell(:pentagon) AND NOT h3_is_valid_cell(:invalid); t -- -- TEST h3_is_res_class_iii -- -- if index is Class III then parent is not SELECT h3_is_res_class_iii(:hexagon) AND NOT h3_is_res_class_iii(h3_cell_to_parent(:hexagon)); t SELECT h3_is_res_class_iii(:pentagon) AND NOT h3_is_res_class_iii(h3_cell_to_parent(:pentagon)); t -- -- TEST h3_is_pentagon -- SELECT h3_is_pentagon(:pentagon) AND NOT h3_is_pentagon(:hexagon); t -- -- TEST h3_get_icosahedron_faces -- SELECT h3_get_icosahedron_faces('851c0047fffffff') = ARRAY[11,6]; t SELECT h3_get_icosahedron_faces('851c004bfffffff') = ARRAY[6]; t h3-pg-4.2.2/h3/test/expected/miscellaneous.out000066400000000000000000000043761475234715600211530ustar00rootroot00000000000000\pset tuples_only on \set degs 90.45 \set rads 1.57865030842887 \set epsilon 0.000000001 \set edge '\'1180326b885fffff\'::h3index' -- -- TEST h3_great_circle_distance -- \set lyon POINT(4.8422, 45.7597) \set paris POINT(2.3508, 48.8567) SELECT h3_great_circle_distance(:lyon, :paris, 'rads') - 0.0615628186794217 < :epsilon; t SELECT h3_great_circle_distance(:lyon, :paris, 'm') - 392217.1598841777 < :epsilon; t SELECT h3_great_circle_distance(:lyon, :paris, 'km') - 392.21715988417765 < :epsilon; t -- test that 'km' is the default unit SELECT h3_great_circle_distance(:lyon, :paris, 'km') = h3_great_circle_distance(:lyon, :paris); t -- -- TEST h3_get_hexagon_area_avg -- SELECT abs(h3_get_hexagon_area_avg(10, 'm') - 15047.50190766437) < :epsilon; t SELECT abs(h3_get_hexagon_area_avg(10, 'km') - 0.01504750190766435) < :epsilon; t SELECT h3_get_hexagon_area_avg(10, 'km') = h3_get_hexagon_area_avg(10); t -- -- TEST h3_cell_area -- \set expected_km2 0.01119834221989390 SELECT abs((h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10), 'm^2') / 1000000) - :expected_km2) < :epsilon; t SELECT abs(h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10), 'km^2') - :expected_km2) < :epsilon; t SELECT h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10), 'rads^2') > 0; t -- default is km^2 SELECT h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10), 'km^2') = h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10)); t -- -- TEST h3_get_hexagon_edge_length_avg -- SELECT h3_get_hexagon_edge_length_avg(10, 'm') - 75.86378287 < :epsilon; t SELECT h3_get_hexagon_edge_length_avg(10, 'km') - 0.075863783 < :epsilon; t SELECT h3_get_hexagon_edge_length_avg(10, 'km') = h3_get_hexagon_edge_length_avg(10); t -- -- TEST h3_edge_length -- SELECT h3_edge_length(:edge, 'rads') > 0; t SELECT h3_edge_length(:edge, 'km') > h3_edge_length(:edge, 'rads'); t SELECT h3_edge_length(:edge, 'm') > h3_edge_length(:edge, 'km'); t SELECT h3_edge_length(:edge) = h3_edge_length(:edge, 'km'); t -- -- TEST h3_get_num_cells -- SELECT h3_get_num_cells(0) = 122; t SELECT h3_get_num_cells(15) = 569707381193162; t -- -- TEST h3_get_res_0_cells -- SELECT COUNT(*) = 122 FROM (SELECT h3_get_res_0_cells()) q; t -- -- TEST h3_get_pentagons -- SELECT COUNT(*) = 12 FROM (SELECT h3_get_pentagons(6)) q; t h3-pg-4.2.2/h3/test/expected/opclass_brin.out000066400000000000000000000006021475234715600207520ustar00rootroot00000000000000\pset tuples_only on \set string '\'801dfffffffffff\'' \set hexagon ':string::h3index' CREATE TABLE h3_test_brin (hex h3index PRIMARY KEY); INSERT INTO h3_test_brin (hex) SELECT * FROM h3_get_res_0_cells(); CREATE INDEX h3_brin ON h3_test_brin USING brin (hex); -- -- Test BRIN operator class -- SELECT hex = :hexagon FROM ( SELECT hex FROM h3_test_brin WHERE hex = :hexagon ) q; t h3-pg-4.2.2/h3/test/expected/opclass_btree.out000066400000000000000000000006141475234715600211240ustar00rootroot00000000000000\pset tuples_only on \set string '\'801dfffffffffff\'' \set hexagon ':string::h3index' CREATE TABLE h3_test_btree (hex h3index PRIMARY KEY); INSERT INTO h3_test_btree (hex) SELECT * from h3_get_res_0_cells(); CREATE INDEX h3_btree ON h3_test_btree USING btree (hex); -- -- TEST b-tree operator class -- SELECT hex = :hexagon FROM ( SELECT hex FROM h3_test_btree WHERE hex = :hexagon ) q; t h3-pg-4.2.2/h3/test/expected/opclass_hash.out000066400000000000000000000007301475234715600207450ustar00rootroot00000000000000\pset tuples_only on CREATE TABLE h3_test_hash (hex h3index PRIMARY KEY); INSERT INTO h3_test_hash (hex) SELECT * from h3_get_res_0_cells(); CREATE INDEX h3_btree ON h3_test_hash USING hash (hex); ERROR: relation "h3_btree" already exists -- -- TEST hash operator class -- SELECT COUNT(hex) = 122 FROM ( SELECT hex FROM h3_test_hash WHERE hex IN (SELECT h3_get_res_0_cells()) ) q; t CREATE TABLE h3_test_hash_part (hex h3index PRIMARY KEY) PARTITION BY HASH (hex); h3-pg-4.2.2/h3/test/expected/opclass_spgist.out000066400000000000000000000014121475234715600213310ustar00rootroot00000000000000\pset tuples_only on \set hexagon '\'831c02fffffffff\'::h3index' CREATE TABLE h3_test_spgist (hex h3index); CREATE INDEX SPGIST_IDX ON h3_test_spgist USING spgist(hex h3index_ops_experimental); INSERT INTO h3_test_spgist (hex) SELECT h3_cell_to_parent(:hexagon); INSERT INTO h3_test_spgist (hex) SELECT h3_cell_to_children(:hexagon); INSERT INTO h3_test_spgist (hex) SELECT h3_cell_to_center_child(:hexagon, 15); -- -- TEST SP-GiST -- SELECT COUNT(*) = 1 FROM h3_test_spgist WHERE hex @> :hexagon; t SELECT COUNT(*) = 8 FROM h3_test_spgist WHERE hex <@ :hexagon; t -- TRUNCATE TABLE h3_test_spgist; INSERT INTO h3_test_spgist (hex) SELECT h3_cell_to_children(h3_cell_to_center_child(:hexagon, 10), 15); SELECT COUNT(*) = 16807 FROM h3_test_spgist WHERE hex <@ :hexagon; t h3-pg-4.2.2/h3/test/expected/regions.out000066400000000000000000000050431475234715600177460ustar00rootroot00000000000000\pset tuples_only on -- res 0 index \set res0index '\'8059fffffffffff\'' -- center hex \set center '\'81583ffffffffff\'' -- 7 child hexes in res 0 index \set solid 'ARRAY(SELECT h3_cell_to_children(:res0index, 1))' -- 6 child hexes in rim of res 0 index \set hollow 'array_remove(:solid, :center)' -- pentagon \set pentagon '\'831c00fffffffff\'::h3index' -- -- TEST h3_polygon_to_cells and h3_cells_to_multi_polygon -- -- h3_polygon_to_cells is inverse of h3_cells_to_multi_polygon for set without holes SELECT array_agg(result) is null FROM ( SELECT h3_polygon_to_cells(exterior, holes, 1) result FROM ( SELECT exterior, holes FROM h3_cells_to_multi_polygon(:solid) ) qq EXCEPT SELECT unnest(:solid) result ) q; t -- h3_polygon_to_cells is inverse of h3_cells_to_multi_polygon for set with a hole SELECT array_agg(result) is null FROM ( SELECT h3_polygon_to_cells(exterior, holes, 1) result FROM ( SELECT exterior, holes FROM h3_cells_to_multi_polygon(:hollow) ) qq EXCEPT SELECT unnest(:hollow) result ) q; t -- h3_polyfill doesn't segfault on NULL value in holes SELECT TRUE FROM ( SELECT h3_polygon_to_cells(exterior, ARRAY[NULL::POLYGON], 1) result FROM ( SELECT exterior, holes FROM h3_cells_to_multi_polygon( ARRAY[:pentagon]::H3Index[] ) ) qq ) q LIMIT 1; t -- h3_polyfill throws on non-polygons CREATE FUNCTION h3_test_polyfill_bad1() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_polyfill(ST_GeomFromText('POINT(-71.160281 42.258729)',4326), 8); RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; CREATE FUNCTION h3_test_polyfill_bad2() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_polyfill(ST_GeomFromText('LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)',4326), 8); RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; SELECT h3_test_polyfill_bad1(); t SELECT h3_test_polyfill_bad2(); t DROP FUNCTION h3_test_polyfill_bad1; DROP FUNCTION h3_test_polyfill_bad2; -- -- TEST h3_polygon_to_cells_experimental -- -- h3_polygon_to_cells is inverse of h3_cells_to_multi_polygon for set without holes SELECT array_agg(result) is null FROM ( SELECT h3_polygon_to_cells_experimental(exterior, holes, 1, 'overlapping') result FROM ( SELECT exterior, holes FROM h3_cells_to_multi_polygon(:solid) ) qq EXCEPT SELECT h3_grid_disk(h3_cell_to_center_child(:res0index), 2) result ) q; t h3-pg-4.2.2/h3/test/expected/traversal.out000066400000000000000000000040401475234715600202770ustar00rootroot00000000000000\pset tuples_only on \set hexagon '\'880326b88dfffff\'' \set origin '\'880326b887fffff\'' \set pentagon '\'831c00fffffffff\'' -- -- TEST h3_grid_disk and h3_grid_ring_unsafe -- -- gridDisk 0 is input index SELECT h3_grid_disk(:hexagon, 0) = :hexagon; t -- gridDisk 2 is same as sum of gridRing 0, 1 and 2 SELECT array_agg(r) is null FROM ( SELECT h3_grid_disk(:hexagon, 2) r EXCEPT ( SELECT h3_grid_ring_unsafe(:hexagon, 0) r UNION SELECT h3_grid_ring_unsafe(:hexagon, 1) r UNION SELECT h3_grid_ring_unsafe(:hexagon, 2) r ) ) q; t -- -- TEST h3_grid_disk_distances -- -- correct number of indexes at distances 0, 1 and 2 for k=2 SELECT COUNT(index) filter (WHERE distance = 0) = 1 AND COUNT(index) filter (WHERE distance = 1) = 6 AND COUNT(index) filter (WHERE distance = 2) = 12 FROM ( SELECT index, distance FROM h3_grid_disk_distances(:hexagon, 2) ) q; t -- same for pentagon SELECT COUNT(index) filter (WHERE distance = 0) = 1 AND COUNT(index) filter (WHERE distance = 1) = 5 AND COUNT(index) filter (WHERE distance = 2) = 10 FROM ( SELECT index, distance FROM h3_grid_disk_distances(:pentagon, 2) ) q; t -- -- TEST h3_grid_path_cells -- SELECT ARRAY(SELECT h3_grid_path_cells('841c023ffffffff', '841c025ffffffff')) = ARRAY['841c023ffffffff','841c027ffffffff','841c025ffffffff']::h3index[]; t -- -- TEST h3_grid_distance -- -- returns 1 for indexes with one index between them SELECT h3_grid_distance('880326b881fffff', '880326b885fffff') = 1; t -- throws for invalid inputs CREATE FUNCTION h3_test_grid_distance_invalid() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_grid_distance('880326b881fffff', h3_cell_to_parent('880326b885fffff')) = -1; RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; SELECT h3_test_grid_distance_invalid(); t -- -- TEST h3_cell_to_local_ij and h3_local_ij_to_cell -- -- they are inverse of each others SELECT :hexagon = h3_local_ij_to_cell(:origin, h3_cell_to_local_ij(:origin, :hexagon)); t h3-pg-4.2.2/h3/test/expected/type.out000066400000000000000000000023031475234715600172550ustar00rootroot00000000000000\pset tuples_only on \set string '\'801dfffffffffff\'' \set asbigint 576988517884755967 \set hexagon ':string::h3index' \set pentagon '\'844c001ffffffff\'::h3index' -- -- TEST operators -- SELECT :hexagon = :hexagon; t SELECT NOT :hexagon = :pentagon; t SELECT NOT :hexagon <> :hexagon; t SELECT :hexagon <> :pentagon; t SELECT :pentagon <@ h3_cell_to_parent(:pentagon); t SELECT bool_and(:pentagon @> c) FROM ( SELECT h3_cell_to_children(:pentagon) c ) q; t -- -- TEST bigint casting -- SELECT :asbigint = :hexagon::bigint; t SELECT :hexagon = :asbigint::h3index; t -- -- TEST binary io -- CREATE TEMPORARY TABLE h3_test_binary_send (hex h3index PRIMARY KEY); CREATE TEMPORARY TABLE h3_test_binary_recv (hex h3index PRIMARY KEY); INSERT INTO h3_test_binary_send (hex) SELECT * from h3_get_res_0_cells(); -- we need to use \copy instead of SQL COPY in order to have relative path \copy h3_test_binary_send TO 'h3_test_binary.bin' (FORMAT binary) \copy h3_test_binary_recv FROM 'h3_test_binary.bin' (FORMAT binary) -- make sure re-imported data matches original data SELECT array_agg(hex) is null FROM ( SELECT hex FROM h3_test_binary_send EXCEPT SELECT hex FROM h3_test_binary_recv ) q; t h3-pg-4.2.2/h3/test/expected/vertex.out000066400000000000000000000012651475234715600176170ustar00rootroot00000000000000\pset tuples_only on \set hexagon '\'880326b885fffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set vertex2 '\'2280326b885fffff\'::h3index' \set geo POINT(65.60200108645547,89.57740563247555) -- -- TEST h3_cell_to_vertex -- SELECT h3_cell_to_vertex(:hexagon, 2) = :vertex2; t -- -- TEST h3_cell_to_vertexes -- SELECT COUNT(*) = 6 FROM ( SELECT h3_cell_to_vertexes(:hexagon) ) q; t SELECT COUNT(*) = 5 FROM ( SELECT h3_cell_to_vertexes(:pentagon) ) q; t -- -- TEST h3_vertex_to_lat_lng -- SELECT h3_vertex_to_lat_lng(:vertex2) ~= :geo; t -- -- TEST h3_is_valid_vertex and -- SELECT h3_is_valid_vertex(:vertex2); t SELECT NOT h3_is_valid_vertex(:hexagon); t h3-pg-4.2.2/h3/test/sql/000077500000000000000000000000001475234715600145435ustar00rootroot00000000000000h3-pg-4.2.2/h3/test/sql/clustering.sql000066400000000000000000000007441475234715600174500ustar00rootroot00000000000000\pset tuples_only on \set string '\'801dfffffffffff\'' \set hexagon ':string::h3index' CREATE TABLE h3_test_clustering (hex h3index PRIMARY KEY); INSERT INTO h3_test_clustering (hex) SELECT * from h3_get_res_0_cells(); CREATE INDEX h3_test_clustering_index ON h3_test_clustering USING btree (hex); CLUSTER h3_test_clustering_index ON h3_test_clustering; -- -- TEST clustering on B-tree -- SELECT hex = :hexagon FROM ( SELECT hex FROM h3_test_clustering WHERE hex = :hexagon ) q; h3-pg-4.2.2/h3/test/sql/edge.sql000066400000000000000000000024561475234715600161770ustar00rootroot00000000000000\pset tuples_only on \set hexagon '\'880326b885fffff\'::h3index' \set neighbor '\'880326b887fffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set edge '\'1180326b885fffff\'::h3index' -- -- TEST h3_are_neighbor_cells -- SELECT h3_are_neighbor_cells(:hexagon, :neighbor); SELECT NOT h3_are_neighbor_cells(:hexagon, :hexagon); -- -- TEST h3_cells_to_directed_edge -- SELECT h3_cells_to_directed_edge(:hexagon, :neighbor) = :edge; -- -- TEST h3_is_valid_directed_edge -- SELECT h3_is_valid_directed_edge(:edge); SELECT NOT h3_is_valid_directed_edge(:hexagon); -- -- TEST h3_get_directed_edge_origin and -- h3_get_directed_edge_destination -- SELECT h3_get_directed_edge_origin(:edge) = :hexagon AND h3_get_directed_edge_destination(:edge) = :neighbor; -- -- TEST h3_directed_edge_to_cells -- SELECT h3_directed_edge_to_cells(:edge) = (:hexagon, :neighbor); -- -- TEST h3_origin_to_directed_edges -- SELECT array_length(array_agg(edge), 1) = 6 FROM ( SELECT h3_origin_to_directed_edges(:hexagon) edge ) q; SELECT array_length(array_agg(edge), 1) = 5 expected FROM ( SELECT h3_origin_to_directed_edges(:pentagon) edge ) q; -- -- TEST h3_directed_edge_to_boundary -- SELECT h3_directed_edge_to_boundary(:edge) ~= polygon '((89.5830164946548,64.7146398954916),(89.5790678021742,64.2872231517217))'h3-pg-4.2.2/h3/test/sql/extension.sql000066400000000000000000000005121475234715600172760ustar00rootroot00000000000000\pset tuples_only on -- -- TEST h3_get_extension_version -- SELECT h3_get_extension_version() ~ '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' OR h3_get_extension_version() = 'unreleased'; h3-pg-4.2.2/h3/test/sql/hierarchy.sql000066400000000000000000000060441475234715600172460ustar00rootroot00000000000000\pset tuples_only on -- neighbouring indexes (one hexagon, one pentagon) at resolution 3 \set hexagon '\'831c02fffffffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set resolution 3 -- -- TEST h3_cell_to_children, h3_cell_to_parent and h3_to_center_chil -- -- all parents of one the children of a hexagon, must be the original hexagon SELECT bool_and(i = :hexagon) FROM ( SELECT h3_cell_to_parent(h3_cell_to_children(:hexagon)) i ) q; -- one child of the parent of a hexagon, must be the original hexagon SELECT bool_or(i = :hexagon) FROM ( SELECT h3_cell_to_children(h3_cell_to_parent(:hexagon)) i ) q; -- all parents of one the children of a pentagon, must be the original pentagon SELECT bool_and(i = :pentagon) FROM ( SELECT h3_cell_to_parent(h3_cell_to_children(:pentagon)) i ) q; -- one child of the parent of a pentagon, must be the original pentagon SELECT bool_or(i = :pentagon) FROM ( SELECT h3_cell_to_children(h3_cell_to_parent(:pentagon)) i ) q; -- hexagon has 7 children SELECT array_length(array_agg(hex), 1) = 7 FROM ( SELECT h3_cell_to_children(:hexagon) hex ) q; -- pentagon has 6 children SELECT array_length(array_agg(hex), 1) = 6 FROM ( SELECT h3_cell_to_children(:pentagon) hex ) q; -- parent is one lower resolution SELECT h3_get_resolution(h3_cell_to_parent(:hexagon)) = :resolution -1; -- all children is one higher resolution SELECT bool_and(r = :resolution +1) FROM ( SELECT h3_get_resolution(h3_cell_to_children(:hexagon)) r ) q; -- parent of center child should be original index SELECT :hexagon = h3_cell_to_parent(h3_cell_to_center_child(:hexagon, 15), :resolution); -- -- TEST h3_cell_to_child_pos and h3_child_pos_to_cell -- SELECT :hexagon = h3_child_pos_to_cell( h3_cell_to_child_pos(:hexagon, :resolution - 1), h3_cell_to_parent(:hexagon), :resolution ); SELECT COUNT(*) = 7 FROM ( SELECT h3_cell_to_child_pos( h3_cell_to_children(:hexagon), :resolution - 1 ) ) q; -- -- TEST h3_compact_cells and h3_uncompact_cells -- -- compacts the children of two hexes into the original two hexes SELECT array_agg(result) is null FROM ( SELECT h3_compact_cells( ARRAY(SELECT h3_cell_to_children(:hexagon) UNION SELECT h3_cell_to_children(:pentagon)) ) result EXCEPT SELECT unnest(ARRAY[:hexagon, :pentagon]) result ) q; -- compact is inverse of uncompact SELECT h3_compact_cells(ARRAY(SELECT h3_uncompact_cells(ARRAY[:hexagon], :resolution))) = :hexagon; -- uncompacts all to same resolution, gives same result as getting children SELECT array_agg(result) is null FROM ( SELECT h3_uncompact_cells(ARRAY( SELECT h3_cell_to_children(:hexagon) UNION SELECT :pentagon ), :resolution +2) result EXCEPT ( SELECT h3_cell_to_children(:hexagon, :resolution +2) result UNION SELECT h3_cell_to_children(:pentagon, :resolution +2) result ) ) q; -- -- TEST h3_cell_to_children_slow -- -- h3_cell_to_children_slow and h3_cell_to_children have same result SELECT array_agg(result) is null FROM ( SELECT h3_cell_to_children_slow(:hexagon, :resolution + 3) result EXCEPT SELECT h3_cell_to_children(:hexagon, :resolution + 3) result ) q; h3-pg-4.2.2/h3/test/sql/indexing.sql000066400000000000000000000037041475234715600170750ustar00rootroot00000000000000\pset tuples_only on -- neighbouring indexes (one hexagon, one pentagon) at resolution 3 \set geo POINT(-144.52399108028, 49.7165031828995) \set hexagon '\'831c02fffffffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set edgecross '\'8003fffffffffff\'::h3index' \set resolution 3 -- -- TEST h3_cell_to_lat_lng and h3_lat_lng_to_cell -- -- convertion to geo works SELECT h3_cell_to_lat_lng(:hexagon) ~= :geo; -- convertion to h3 index works SELECT h3_lat_lng_to_cell(:geo, :resolution) = :hexagon; -- h3_cell_to_lat_lng is inverse of h3_lat_lng_to_cell SELECT h3_cell_to_lat_lng(i) ~= :geo AND h3_get_resolution(i) = :resolution FROM ( SELECT h3_lat_lng_to_cell(:geo, :resolution) AS i ) AS q; -- h3_lat_lng_to_cell is inverse of h3_cell_to_lat_lng SELECT h3_lat_lng_to_cell(g, r) = :hexagon FROM ( SELECT h3_cell_to_lat_lng(:hexagon) AS g, h3_get_resolution(:hexagon) AS r ) AS q; -- same for pentagon SELECT h3_lat_lng_to_cell(g, r) = :pentagon FROM ( SELECT h3_cell_to_lat_lng(:pentagon) AS g, h3_get_resolution(:pentagon) AS r ) AS q; -- -- TEST h3_cell_to_boundary -- -- polyfill of geo boundary returns original index SELECT h3_polygon_to_cells(h3_cell_to_boundary(:hexagon), null, :resolution) = :hexagon; -- same for pentagon SELECT h3_polygon_to_cells(h3_cell_to_boundary(:pentagon), null, :resolution) = :pentagon; -- the boundary of an edgecrossing index is different with flag set to true SELECT h3_cell_to_boundary(:hexagon) ~= h3_cell_to_boundary(:hexagon, true) AND NOT h3_cell_to_boundary(:edgecross) ~= h3_cell_to_boundary(:edgecross, true); -- cell to parent RES_MISMATCH CREATE FUNCTION h3_fail_indexing_cell_to_parent() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_cell_to_parent('831c02fffffffff', 10); RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; SELECT h3_fail_indexing_cell_to_parent(); DROP FUNCTION h3_fail_indexing_cell_to_parent;h3-pg-4.2.2/h3/test/sql/inspection.sql000066400000000000000000000024141475234715600174400ustar00rootroot00000000000000\pset tuples_only on -- neighbouring indexes (one hexagon, one pentagon) at resolution 3 \set invalid '\'0\'' \set hexagon '\'831c02fffffffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set resolution 3 -- -- TEST h3_get_resolution -- SELECT h3_get_resolution(:hexagon) = :resolution AND h3_get_resolution(:pentagon) = :resolution; -- -- TEST h3_get_base_cell_number -- -- base cell is same for parents SELECT h3_get_base_cell_number(:hexagon) = h3_get_base_cell_number(h3_cell_to_parent(:hexagon)); SELECT h3_get_base_cell_number(:pentagon) = h3_get_base_cell_number(h3_cell_to_parent(:pentagon)); -- -- TEST h3_is_valid_cell -- SELECT h3_is_valid_cell(:hexagon) AND h3_is_valid_cell(:pentagon) AND NOT h3_is_valid_cell(:invalid); -- -- TEST h3_is_res_class_iii -- -- if index is Class III then parent is not SELECT h3_is_res_class_iii(:hexagon) AND NOT h3_is_res_class_iii(h3_cell_to_parent(:hexagon)); SELECT h3_is_res_class_iii(:pentagon) AND NOT h3_is_res_class_iii(h3_cell_to_parent(:pentagon)); -- -- TEST h3_is_pentagon -- SELECT h3_is_pentagon(:pentagon) AND NOT h3_is_pentagon(:hexagon); -- -- TEST h3_get_icosahedron_faces -- SELECT h3_get_icosahedron_faces('851c0047fffffff') = ARRAY[11,6]; SELECT h3_get_icosahedron_faces('851c004bfffffff') = ARRAY[6]; h3-pg-4.2.2/h3/test/sql/miscellaneous.sql000066400000000000000000000042721475234715600201340ustar00rootroot00000000000000\pset tuples_only on \set degs 90.45 \set rads 1.57865030842887 \set epsilon 0.000000001 \set edge '\'1180326b885fffff\'::h3index' -- -- TEST h3_great_circle_distance -- \set lyon POINT(4.8422, 45.7597) \set paris POINT(2.3508, 48.8567) SELECT h3_great_circle_distance(:lyon, :paris, 'rads') - 0.0615628186794217 < :epsilon; SELECT h3_great_circle_distance(:lyon, :paris, 'm') - 392217.1598841777 < :epsilon; SELECT h3_great_circle_distance(:lyon, :paris, 'km') - 392.21715988417765 < :epsilon; -- test that 'km' is the default unit SELECT h3_great_circle_distance(:lyon, :paris, 'km') = h3_great_circle_distance(:lyon, :paris); -- -- TEST h3_get_hexagon_area_avg -- SELECT abs(h3_get_hexagon_area_avg(10, 'm') - 15047.50190766437) < :epsilon; SELECT abs(h3_get_hexagon_area_avg(10, 'km') - 0.01504750190766435) < :epsilon; SELECT h3_get_hexagon_area_avg(10, 'km') = h3_get_hexagon_area_avg(10); -- -- TEST h3_cell_area -- \set expected_km2 0.01119834221989390 SELECT abs((h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10), 'm^2') / 1000000) - :expected_km2) < :epsilon; SELECT abs(h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10), 'km^2') - :expected_km2) < :epsilon; SELECT h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10), 'rads^2') > 0; -- default is km^2 SELECT h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10), 'km^2') = h3_cell_area(h3_lat_lng_to_cell(POINT(0, 0), 10)); -- -- TEST h3_get_hexagon_edge_length_avg -- SELECT h3_get_hexagon_edge_length_avg(10, 'm') - 75.86378287 < :epsilon; SELECT h3_get_hexagon_edge_length_avg(10, 'km') - 0.075863783 < :epsilon; SELECT h3_get_hexagon_edge_length_avg(10, 'km') = h3_get_hexagon_edge_length_avg(10); -- -- TEST h3_edge_length -- SELECT h3_edge_length(:edge, 'rads') > 0; SELECT h3_edge_length(:edge, 'km') > h3_edge_length(:edge, 'rads'); SELECT h3_edge_length(:edge, 'm') > h3_edge_length(:edge, 'km'); SELECT h3_edge_length(:edge) = h3_edge_length(:edge, 'km'); -- -- TEST h3_get_num_cells -- SELECT h3_get_num_cells(0) = 122; SELECT h3_get_num_cells(15) = 569707381193162; -- -- TEST h3_get_res_0_cells -- SELECT COUNT(*) = 122 FROM (SELECT h3_get_res_0_cells()) q; -- -- TEST h3_get_pentagons -- SELECT COUNT(*) = 12 FROM (SELECT h3_get_pentagons(6)) q; h3-pg-4.2.2/h3/test/sql/opclass_brin.sql000066400000000000000000000005771475234715600177530ustar00rootroot00000000000000\pset tuples_only on \set string '\'801dfffffffffff\'' \set hexagon ':string::h3index' CREATE TABLE h3_test_brin (hex h3index PRIMARY KEY); INSERT INTO h3_test_brin (hex) SELECT * FROM h3_get_res_0_cells(); CREATE INDEX h3_brin ON h3_test_brin USING brin (hex); -- -- Test BRIN operator class -- SELECT hex = :hexagon FROM ( SELECT hex FROM h3_test_brin WHERE hex = :hexagon ) q; h3-pg-4.2.2/h3/test/sql/opclass_btree.sql000066400000000000000000000006111475234715600201070ustar00rootroot00000000000000\pset tuples_only on \set string '\'801dfffffffffff\'' \set hexagon ':string::h3index' CREATE TABLE h3_test_btree (hex h3index PRIMARY KEY); INSERT INTO h3_test_btree (hex) SELECT * from h3_get_res_0_cells(); CREATE INDEX h3_btree ON h3_test_btree USING btree (hex); -- -- TEST b-tree operator class -- SELECT hex = :hexagon FROM ( SELECT hex FROM h3_test_btree WHERE hex = :hexagon ) q; h3-pg-4.2.2/h3/test/sql/opclass_hash.sql000066400000000000000000000006531475234715600177370ustar00rootroot00000000000000\pset tuples_only on CREATE TABLE h3_test_hash (hex h3index PRIMARY KEY); INSERT INTO h3_test_hash (hex) SELECT * from h3_get_res_0_cells(); CREATE INDEX h3_btree ON h3_test_hash USING hash (hex); -- -- TEST hash operator class -- SELECT COUNT(hex) = 122 FROM ( SELECT hex FROM h3_test_hash WHERE hex IN (SELECT h3_get_res_0_cells()) ) q; CREATE TABLE h3_test_hash_part (hex h3index PRIMARY KEY) PARTITION BY HASH (hex);h3-pg-4.2.2/h3/test/sql/opclass_spgist.sql000066400000000000000000000014021475234715600203160ustar00rootroot00000000000000\pset tuples_only on \set hexagon '\'831c02fffffffff\'::h3index' CREATE TABLE h3_test_spgist (hex h3index); CREATE INDEX SPGIST_IDX ON h3_test_spgist USING spgist(hex h3index_ops_experimental); INSERT INTO h3_test_spgist (hex) SELECT h3_cell_to_parent(:hexagon); INSERT INTO h3_test_spgist (hex) SELECT h3_cell_to_children(:hexagon); INSERT INTO h3_test_spgist (hex) SELECT h3_cell_to_center_child(:hexagon, 15); -- -- TEST SP-GiST -- SELECT COUNT(*) = 1 FROM h3_test_spgist WHERE hex @> :hexagon; SELECT COUNT(*) = 8 FROM h3_test_spgist WHERE hex <@ :hexagon; -- TRUNCATE TABLE h3_test_spgist; INSERT INTO h3_test_spgist (hex) SELECT h3_cell_to_children(h3_cell_to_center_child(:hexagon, 10), 15); SELECT COUNT(*) = 16807 FROM h3_test_spgist WHERE hex <@ :hexagon; h3-pg-4.2.2/h3/test/sql/regions.sql000066400000000000000000000050221475234715600167310ustar00rootroot00000000000000\pset tuples_only on -- res 0 index \set res0index '\'8059fffffffffff\'' -- center hex \set center '\'81583ffffffffff\'' -- 7 child hexes in res 0 index \set solid 'ARRAY(SELECT h3_cell_to_children(:res0index, 1))' -- 6 child hexes in rim of res 0 index \set hollow 'array_remove(:solid, :center)' -- pentagon \set pentagon '\'831c00fffffffff\'::h3index' -- -- TEST h3_polygon_to_cells and h3_cells_to_multi_polygon -- -- h3_polygon_to_cells is inverse of h3_cells_to_multi_polygon for set without holes SELECT array_agg(result) is null FROM ( SELECT h3_polygon_to_cells(exterior, holes, 1) result FROM ( SELECT exterior, holes FROM h3_cells_to_multi_polygon(:solid) ) qq EXCEPT SELECT unnest(:solid) result ) q; -- h3_polygon_to_cells is inverse of h3_cells_to_multi_polygon for set with a hole SELECT array_agg(result) is null FROM ( SELECT h3_polygon_to_cells(exterior, holes, 1) result FROM ( SELECT exterior, holes FROM h3_cells_to_multi_polygon(:hollow) ) qq EXCEPT SELECT unnest(:hollow) result ) q; -- h3_polyfill doesn't segfault on NULL value in holes SELECT TRUE FROM ( SELECT h3_polygon_to_cells(exterior, ARRAY[NULL::POLYGON], 1) result FROM ( SELECT exterior, holes FROM h3_cells_to_multi_polygon( ARRAY[:pentagon]::H3Index[] ) ) qq ) q LIMIT 1; -- h3_polyfill throws on non-polygons CREATE FUNCTION h3_test_polyfill_bad1() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_polyfill(ST_GeomFromText('POINT(-71.160281 42.258729)',4326), 8); RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; CREATE FUNCTION h3_test_polyfill_bad2() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_polyfill(ST_GeomFromText('LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)',4326), 8); RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; SELECT h3_test_polyfill_bad1(); SELECT h3_test_polyfill_bad2(); DROP FUNCTION h3_test_polyfill_bad1; DROP FUNCTION h3_test_polyfill_bad2; -- -- TEST h3_polygon_to_cells_experimental -- -- h3_polygon_to_cells is inverse of h3_cells_to_multi_polygon for set without holes SELECT array_agg(result) is null FROM ( SELECT h3_polygon_to_cells_experimental(exterior, holes, 1, 'overlapping') result FROM ( SELECT exterior, holes FROM h3_cells_to_multi_polygon(:solid) ) qq EXCEPT SELECT h3_grid_disk(h3_cell_to_center_child(:res0index), 2) result ) q; h3-pg-4.2.2/h3/test/sql/traversal.sql000066400000000000000000000040161475234715600172700ustar00rootroot00000000000000\pset tuples_only on \set hexagon '\'880326b88dfffff\'' \set origin '\'880326b887fffff\'' \set pentagon '\'831c00fffffffff\'' -- -- TEST h3_grid_disk and h3_grid_ring_unsafe -- -- gridDisk 0 is input index SELECT h3_grid_disk(:hexagon, 0) = :hexagon; -- gridDisk 2 is same as sum of gridRing 0, 1 and 2 SELECT array_agg(r) is null FROM ( SELECT h3_grid_disk(:hexagon, 2) r EXCEPT ( SELECT h3_grid_ring_unsafe(:hexagon, 0) r UNION SELECT h3_grid_ring_unsafe(:hexagon, 1) r UNION SELECT h3_grid_ring_unsafe(:hexagon, 2) r ) ) q; -- -- TEST h3_grid_disk_distances -- -- correct number of indexes at distances 0, 1 and 2 for k=2 SELECT COUNT(index) filter (WHERE distance = 0) = 1 AND COUNT(index) filter (WHERE distance = 1) = 6 AND COUNT(index) filter (WHERE distance = 2) = 12 FROM ( SELECT index, distance FROM h3_grid_disk_distances(:hexagon, 2) ) q; -- same for pentagon SELECT COUNT(index) filter (WHERE distance = 0) = 1 AND COUNT(index) filter (WHERE distance = 1) = 5 AND COUNT(index) filter (WHERE distance = 2) = 10 FROM ( SELECT index, distance FROM h3_grid_disk_distances(:pentagon, 2) ) q; -- -- TEST h3_grid_path_cells -- SELECT ARRAY(SELECT h3_grid_path_cells('841c023ffffffff', '841c025ffffffff')) = ARRAY['841c023ffffffff','841c027ffffffff','841c025ffffffff']::h3index[]; -- -- TEST h3_grid_distance -- -- returns 1 for indexes with one index between them SELECT h3_grid_distance('880326b881fffff', '880326b885fffff') = 1; -- throws for invalid inputs CREATE FUNCTION h3_test_grid_distance_invalid() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_grid_distance('880326b881fffff', h3_cell_to_parent('880326b885fffff')) = -1; RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; SELECT h3_test_grid_distance_invalid(); -- -- TEST h3_cell_to_local_ij and h3_local_ij_to_cell -- -- they are inverse of each others SELECT :hexagon = h3_local_ij_to_cell(:origin, h3_cell_to_local_ij(:origin, :hexagon));h3-pg-4.2.2/h3/test/sql/type.sql000066400000000000000000000022451475234715600162500ustar00rootroot00000000000000\pset tuples_only on \set string '\'801dfffffffffff\'' \set asbigint 576988517884755967 \set hexagon ':string::h3index' \set pentagon '\'844c001ffffffff\'::h3index' -- -- TEST operators -- SELECT :hexagon = :hexagon; SELECT NOT :hexagon = :pentagon; SELECT NOT :hexagon <> :hexagon; SELECT :hexagon <> :pentagon; SELECT :pentagon <@ h3_cell_to_parent(:pentagon); SELECT bool_and(:pentagon @> c) FROM ( SELECT h3_cell_to_children(:pentagon) c ) q; -- -- TEST bigint casting -- SELECT :asbigint = :hexagon::bigint; SELECT :hexagon = :asbigint::h3index; -- -- TEST binary io -- CREATE TEMPORARY TABLE h3_test_binary_send (hex h3index PRIMARY KEY); CREATE TEMPORARY TABLE h3_test_binary_recv (hex h3index PRIMARY KEY); INSERT INTO h3_test_binary_send (hex) SELECT * from h3_get_res_0_cells(); -- we need to use \copy instead of SQL COPY in order to have relative path \copy h3_test_binary_send TO 'h3_test_binary.bin' (FORMAT binary) \copy h3_test_binary_recv FROM 'h3_test_binary.bin' (FORMAT binary) -- make sure re-imported data matches original data SELECT array_agg(hex) is null FROM ( SELECT hex FROM h3_test_binary_send EXCEPT SELECT hex FROM h3_test_binary_recv ) q; h3-pg-4.2.2/h3/test/sql/vertex.sql000066400000000000000000000012451475234715600166030ustar00rootroot00000000000000\pset tuples_only on \set hexagon '\'880326b885fffff\'::h3index' \set pentagon '\'831c00fffffffff\'::h3index' \set vertex2 '\'2280326b885fffff\'::h3index' \set geo POINT(65.60200108645547,89.57740563247555) -- -- TEST h3_cell_to_vertex -- SELECT h3_cell_to_vertex(:hexagon, 2) = :vertex2; -- -- TEST h3_cell_to_vertexes -- SELECT COUNT(*) = 6 FROM ( SELECT h3_cell_to_vertexes(:hexagon) ) q; SELECT COUNT(*) = 5 FROM ( SELECT h3_cell_to_vertexes(:pentagon) ) q; -- -- TEST h3_vertex_to_lat_lng -- SELECT h3_vertex_to_lat_lng(:vertex2) ~= :geo; -- -- TEST h3_is_valid_vertex and -- SELECT h3_is_valid_vertex(:vertex2); SELECT NOT h3_is_valid_vertex(:hexagon);h3-pg-4.2.2/h3_postgis/000077500000000000000000000000001475234715600145355ustar00rootroot00000000000000h3-pg-4.2.2/h3_postgis/CMakeLists.txt000066400000000000000000000025301475234715600172750ustar00rootroot00000000000000PostgreSQL_add_extension(postgresql_h3_postgis RELOCATABLE NAME h3_postgis COMMENT "H3 PostGIS integration" VERSION ${INSTALL_VERSION} COMPONENT ${PROJECT_NAME} REQUIRES h3 postgis postgis_raster SOURCES src/init.c src/wkb_bbox3.c src/wkb_indexing.c src/wkb_linked_geo.c src/wkb_regions.c src/wkb_split.c src/wkb_vect3.c src/wkb.c INSTALLS sql/install/01-indexing.sql sql/install/03-traversal.sql sql/install/05-regions.sql sql/install/10-operators.sql sql/install/20-casts.sql sql/install/30-wkb.sql sql/install/40-rasters.sql sql/install/99-deprecated.sql UPDATES sql/updates/h3_postgis--4.0.0.sql sql/updates/h3_postgis--4.0.0--4.0.1.sql sql/updates/h3_postgis--4.0.1--4.0.2.sql sql/updates/h3_postgis--4.0.2--4.0.3.sql sql/updates/h3_postgis--4.0.3--4.1.0.sql sql/updates/h3_postgis--4.1.0--4.1.1.sql sql/updates/h3_postgis--4.1.1--4.1.2.sql sql/updates/h3_postgis--4.1.2--4.1.3.sql sql/updates/h3_postgis--4.1.3--4.1.4.sql sql/updates/h3_postgis--4.1.4--4.2.0.sql sql/updates/h3_postgis--4.2.0--4.2.1.sql sql/updates/h3_postgis--4.2.1--4.2.2.sql ) # link target_link_libraries(postgresql_h3_postgis PRIVATE postgresql_h3_shared h3) # test if(BUILD_TESTING AND PostgreSQL_PostGIS_FOUND) add_subdirectory(test) endif() h3-pg-4.2.2/h3_postgis/sql/000077500000000000000000000000001475234715600153345ustar00rootroot00000000000000h3-pg-4.2.2/h3_postgis/sql/install/000077500000000000000000000000001475234715600170025ustar00rootroot00000000000000h3-pg-4.2.2/h3_postgis/sql/install/01-indexing.sql000066400000000000000000000063331475234715600215530ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| The `GEOMETRY` data passed to `h3-pg` PostGIS functions should --| be in SRID 4326. This is an expectation of the core H3 library. --| Using other SRIDs, such as 3857, can result in either errors or --| invalid data depending on the function. --| For example, the `h3_polygon_to_cells()` function will fail with --| an error in this scenario while the `h3_lat_lng_to_cell()` function --| will return an invalid geometry. --| # PostGIS Indexing Functions --@ availability: 4.0.0 --@ refid: h3_lat_lng_to_cell_geometry CREATE OR REPLACE FUNCTION h3_lat_lng_to_cell(geometry, resolution integer) RETURNS h3index AS $$ SELECT h3_lat_lng_to_cell($1::point, $2); $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; COMMENT ON FUNCTION h3_lat_lng_to_cell(geometry, resolution integer) IS 'Indexes the location at the specified resolution.'; --@ availability: 4.0.0 --@ refid: h3_lat_lng_to_cell_geography CREATE OR REPLACE FUNCTION h3_lat_lng_to_cell(geography, resolution integer) RETURNS h3index AS $$ SELECT h3_lat_lng_to_cell($1::geometry, $2); $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; COMMENT ON FUNCTION h3_lat_lng_to_cell(geometry, resolution integer) IS 'Indexes the location at the specified resolution.'; --@ availability: 4.0.0 --@ refid: h3_cell_to_geometry CREATE OR REPLACE FUNCTION h3_cell_to_geometry(h3index) RETURNS geometry AS $$ SELECT ST_SetSRID(h3_cell_to_lat_lng($1)::geometry, 4326) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_geometry(h3index) IS 'Finds the centroid of the index.'; --@ availability: 4.0.0 --@ refid: h3_cell_to_geography CREATE OR REPLACE FUNCTION h3_cell_to_geography(h3index) RETURNS geography AS $$ SELECT h3_cell_to_geometry($1)::geography $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_geography(h3index) IS 'Finds the centroid of the index.'; --@ availability: 4.0.0 --@ refid: h3_cell_to_boundary_geometry CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geometry(h3index) RETURNS geometry AS $$ SELECT h3_cell_to_boundary_wkb($1)::geometry $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_boundary_geometry(h3index) IS 'Finds the boundary of the index. Splits polygons when crossing 180th meridian.'; --@ availability: 4.0.0 --@ refid: h3_cell_to_boundary_geography CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geography(h3index) RETURNS geography AS $$ SELECT h3_cell_to_boundary_wkb($1)::geography $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; COMMENT ON FUNCTION h3_cell_to_boundary_geography(h3index) IS 'Finds the boundary of the index. Splits polygons when crossing 180th meridian.'; h3-pg-4.2.2/h3_postgis/sql/install/03-traversal.sql000066400000000000000000000041741475234715600217540ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # PostGIS Grid Traversal Functions --@ availability: 4.1.0 --@ refid: h3_grid_path_cells_recursive CREATE OR REPLACE FUNCTION h3_grid_path_cells_recursive(origin h3index, destination h3index) RETURNS SETOF h3index AS $$ BEGIN IF (SELECT origin != destination AND NOT h3_are_neighbor_cells(origin, destination) AND ((base1 != base2 AND NOT h3_are_neighbor_cells(base1, base2)) OR ((h3_is_pentagon(base1) OR h3_is_pentagon(base2)) AND NOT ( h3_get_icosahedron_faces(origin) && h3_get_icosahedron_faces(destination)))) FROM ( SELECT h3_cell_to_parent(origin, 0) AS base1, h3_cell_to_parent(destination, 0) AS base2) AS t) THEN RETURN QUERY WITH points AS ( SELECT h3_cell_to_geometry(origin) AS g1, h3_cell_to_geometry(destination) AS g2), cells AS ( SELECT h3_lat_lng_to_cell( ST_Centroid(ST_MakeLine(g1, g2)::geography), h3_get_resolution(origin)) AS middle FROM points) SELECT h3_grid_path_cells_recursive(origin, middle) FROM cells UNION SELECT h3_grid_path_cells_recursive(middle, destination) FROM cells; ELSE RETURN QUERY SELECT h3_grid_path_cells(origin, destination); END IF; END; $$ LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL SAFE; h3-pg-4.2.2/h3_postgis/sql/install/05-regions.sql000066400000000000000000000105341475234715600214160ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # PostGIS Region Functions --@ availability: 4.0.0 --@ refid: h3_polygon_to_cells_geometry CREATE OR REPLACE FUNCTION h3_polygon_to_cells(multi geometry, resolution integer) RETURNS SETOF h3index AS $$ SELECT h3_polygon_to_cells(exterior, holes, resolution) FROM ( SELECT -- extract exterior ring of each polygon ST_MakePolygon(ST_ExteriorRing(poly))::polygon exterior, -- extract holes of each polygon (SELECT array_agg(hole) FROM ( SELECT ST_MakePolygon(ST_InteriorRingN( poly, generate_series(1, ST_NumInteriorRings(poly)) ))::polygon AS hole ) q_hole ) holes -- extract single polygons from multipolygon FROM ( select (st_dump(multi)).geom as poly ) q_poly GROUP BY poly ) h3_polygon_to_cells; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT --@ availability: 4.0.0 --@ refid: h3_polygon_to_cells_geography CREATE OR REPLACE FUNCTION h3_polygon_to_cells(multi geography, resolution integer) RETURNS SETOF h3index AS $$ SELECT h3_polygon_to_cells($1::geometry, $2) $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT --@ availability: 4.1.0 --@ refid: h3_cells_to_multi_polygon_geometry CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon_geometry(h3index[]) RETURNS geometry AS $$ SELECT h3_cells_to_multi_polygon_wkb($1)::geometry $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; --@ availability: 4.1.0 --@ refid: h3_cells_to_multi_polygon_geography CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon_geography(h3index[]) RETURNS geography AS $$ SELECT h3_cells_to_multi_polygon_wkb($1)::geography $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; --@ availability: 4.1.0 --@ refid: h3_cells_to_multi_polygon_geometry_agg CREATE AGGREGATE h3_cells_to_multi_polygon_geometry(h3index) ( sfunc = array_append, stype = h3index[], finalfunc = h3_cells_to_multi_polygon_geometry, parallel = safe ); --@ availability: 4.1.0 --@ refid: h3_cells_to_multi_polygon_geography_agg CREATE AGGREGATE h3_cells_to_multi_polygon_geography(h3index) ( sfunc = array_append, stype = h3index[], finalfunc = h3_cells_to_multi_polygon_geography, parallel = safe ); --@ availability: 4.2.0 --@ refid: h3_polygon_to_cells_geometry_experimental CREATE OR REPLACE FUNCTION h3_polygon_to_cells_experimental(multi geometry, resolution integer, containment_mode text DEFAULT 'center') RETURNS SETOF h3index AS $$ SELECT h3_polygon_to_cells_experimental(exterior, holes, resolution, containment_mode) FROM ( SELECT -- extract exterior ring of each polygon ST_MakePolygon(ST_ExteriorRing(poly))::polygon exterior, -- extract holes of each polygon (SELECT array_agg(hole) FROM ( SELECT ST_MakePolygon(ST_InteriorRingN( poly, generate_series(1, ST_NumInteriorRings(poly)) ))::polygon AS hole ) q_hole ) holes -- extract single polygons from multipolygon FROM ( select (st_dump(multi)).geom as poly ) q_poly GROUP BY poly ) h3_polygon_to_cells; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT --@ availability: 4.2.0 --@ refid: h3_polygon_to_cells_geography_experimental CREATE OR REPLACE FUNCTION h3_polygon_to_cells_experimental(multi geography, resolution integer, containment_mode text DEFAULT 'center') RETURNS SETOF h3index AS $$ SELECT h3_polygon_to_cells_experimental($1::geometry, $2, $3) $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT h3-pg-4.2.2/h3_postgis/sql/install/10-operators.sql000066400000000000000000000020501475234715600217540ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ --| # PostGIS Operators --@ availability: 4.1.3 CREATE OPERATOR @ ( PROCEDURE = h3_lat_lng_to_cell, LEFTARG = geometry, RIGHTARG = integer ); COMMENT ON OPERATOR @ (geometry, integer) IS 'Index geometry at specified resolution.'; --@ availability: 4.1.3 CREATE OPERATOR @ ( PROCEDURE = h3_lat_lng_to_cell, LEFTARG = geography, RIGHTARG = integer ); COMMENT ON OPERATOR @ (geography, integer) IS 'Index geography at specified resolution.'; h3-pg-4.2.2/h3_postgis/sql/install/20-casts.sql000066400000000000000000000016161475234715600210630ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- ---------- ---------- ---------- ---------- ---------- ---------- ---------- --| # PostGIS casts --@ availability: 0.3.0 CREATE CAST (h3index AS geometry) WITH FUNCTION h3_cell_to_geometry(h3index); --@ availability: 0.3.0 CREATE CAST (h3index AS geography) WITH FUNCTION h3_cell_to_geography(h3index); h3-pg-4.2.2/h3_postgis/sql/install/30-wkb.sql000066400000000000000000000026541475234715600205350ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ --| # WKB indexing functions --@ availability: 4.1.0 CREATE OR REPLACE FUNCTION h3_cell_to_boundary_wkb(cell h3index) RETURNS bytea AS 'h3_postgis' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary_wkb(h3index) IS 'Finds the boundary of the index, converts to EWKB. Splits polygons when crossing 180th meridian. This function has to return WKB since Postgres does not provide multipolygon type.'; --| # WKB regions functions --@ availability: 4.1.0 CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon_wkb(h3index[]) RETURNS bytea AS 'h3_postgis' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cells_to_multi_polygon_wkb(h3index[]) IS 'Create a LinkedGeoPolygon describing the outline(s) of a set of hexagons, converts to EWKB. Splits polygons when crossing 180th meridian.'; h3-pg-4.2.2/h3_postgis/sql/install/40-rasters.sql000066400000000000000000000605261475234715600214400ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ --| # Raster processing functions -- Get nodata value for ST_Clip function -- ST_Clip sets nodata pixel values to minimum value by default, but it won't -- set band nodata value in this case, which we need later for filtering dumped -- values. CREATE OR REPLACE FUNCTION __h3_raster_band_nodata( rast raster, nband integer) RETURNS double precision AS $$ SELECT coalesce( ST_BandNoDataValue(rast, nband), ST_MinPossibleValue(ST_BandPixelType(rast, nband))); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_to_polygon( rast raster, nband integer) RETURNS geometry AS $$ SELECT ST_MinConvexHull(rast, nband); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; -- Area of a pixel close to the center of raster polygon, in meters CREATE OR REPLACE FUNCTION __h3_raster_polygon_pixel_area( rast raster, poly geometry) RETURNS double precision AS $$ SELECT ST_Area( ST_Transform( ST_PixelAsPolygon( rast, ST_WorldToRasterCoordX(rast, c), ST_WorldToRasterCoordY(rast, c)), 4326)::geography) FROM ST_Centroid(poly) AS c $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_centroid_cell( poly geometry, resolution integer) RETURNS h3index AS $$ DECLARE cell h3index := h3_lat_lng_to_cell(ST_Transform(ST_Centroid(poly), 4326), resolution); BEGIN IF h3_is_pentagon(cell) THEN SELECT h3 INTO cell FROM h3_grid_disk(cell) AS h3 WHERE h3 != cell LIMIT 1; END IF; RETURN cell; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; -- Area of a cell close to the center of raster polygon, in meters CREATE OR REPLACE FUNCTION __h3_raster_polygon_centroid_cell_area( poly geometry, resolution integer) RETURNS double precision AS $$ SELECT ST_Area( h3_cell_to_boundary_geography( __h3_raster_polygon_centroid_cell(poly, resolution))); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; -- Get list of cells inside of the raster polygon, -- buffered by `buffer` value (in meters). -- If SRID != 4326 then additionally buffer by 1 pixel to account for transformation. CREATE OR REPLACE FUNCTION __h3_raster_polygon_to_cells( rast raster, poly geometry, resolution integer, buffer double precision) RETURNS SETOF h3index AS $$ DECLARE buffered geometry := poly; BEGIN IF ST_SRID(rast) != 4326 THEN buffered := ST_Transform( ST_Buffer( poly, greatest(ST_PixelWidth(rast), ST_PixelHeight(rast)), 'join=mitre'), 4326); END IF; IF buffer > 0.0 THEN RETURN QUERY SELECT h3_polygon_to_cells( ST_Buffer( buffered::geography, buffer, 'join=mitre'), resolution); ELSE RETURN QUERY SELECT h3_polygon_to_cells(buffered, resolution); END IF; END $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; -- Get geometries of H3 cells interesecting raster polygon. CREATE OR REPLACE FUNCTION __h3_raster_polygon_to_cell_boundaries_intersects( rast raster, poly geometry, resolution integer) RETURNS TABLE (h3 h3index, geom geometry) AS $$ SELECT h3, geom FROM __h3_raster_polygon_to_cells( rast, poly, resolution, h3_get_hexagon_edge_length_avg(resolution, 'm') * 1.3 ) AS h3, ST_Transform(h3_cell_to_boundary_geometry(h3), ST_SRID(rast)) AS geom WHERE ST_Intersects(geom, poly); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; -- Get raster coordinates of H3 cells with centroids inside the raster polygon CREATE OR REPLACE FUNCTION __h3_raster_polygon_to_cell_coords_centroid( rast raster, poly geometry, resolution integer) RETURNS TABLE (h3 h3index, x integer, y integer) AS $$ WITH geoms AS ( SELECT h3, ST_Transform( h3_cell_to_geometry(h3), ST_SRID(poly) ) AS geom FROM ( SELECT __h3_raster_polygon_to_cells(rast, poly, resolution, 0.0) AS h3 ) t), coords AS ( SELECT h3, ST_WorldToRasterCoordX(rast, geom) AS x, ST_WorldToRasterCoordY(rast, geom) AS y FROM geoms) SELECT h3, x, y FROM coords WHERE x BETWEEN 1 AND ST_Width(rast) AND y BETWEEN 1 AND ST_Height(rast); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_to_cell_parts( rast raster, poly geometry, resolution integer, nband integer) RETURNS TABLE (h3 h3index, part raster) AS $$ DECLARE nodata CONSTANT double precision := __h3_raster_band_nodata(rast, nband); BEGIN RETURN QUERY SELECT c.h3, p AS part FROM __h3_raster_polygon_to_cell_boundaries_intersects(rast, poly, resolution) AS c, ST_Clip(rast, nband, c.geom, nodata, TRUE) AS p WHERE NOT ST_BandIsNoData(p, nband); END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; -- Get values corresponding to all H3 cells with centroids inside the -- raster polygon. Assumes cell area is less than pixel area. CREATE OR REPLACE FUNCTION __h3_raster_polygon_subpixel_cell_values( rast raster, poly geometry, resolution integer, nband integer) RETURNS TABLE (h3 h3index, val double precision) AS $$ SELECT h3, ST_Value(rast, nband, x, y) AS val FROM __h3_raster_polygon_to_cell_coords_centroid(rast, poly, resolution); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; --| ## Continuous raster data --| --| For rasters with pixel values representing continuous data (temperature, humidity, --| elevation), the data inside H3 cells can be summarized by calculating number of --| pixels, sum, mean, standard deviation, min and max for each cell inside a raster --| and grouping these stats across multiple rasters by H3 index. --| --| ``` --| SELECT --| (summary).h3 AS h3, --| (h3_raster_summary_stats_agg((summary).stats)).* --| FROM ( --| SELECT h3_raster_summary(rast, 8) AS summary --| FROM rasters --| ) t --| GROUP BY 1; --| --| h3 | count | sum | mean | stddev | min | max --| -----------------+-------+--------------------+---------------------+--------------------+-------+------------------ --| 882d638189fffff | 10 | 4.607657432556152 | 0.46076574325561526 | 1.3822972297668457 | 0 | 4.607657432556152 --| 882d64c4d1fffff | 10 | 3.6940908953547478 | 0.3694090895354748 | 1.099336879464068 | 0 | 3.667332887649536 --| 882d607431fffff | 11 | 6.219290263950825 | 0.5653900239955295 | 1.7624673707119065 | 0 | 6.13831996917724 --| <...> --| ``` -- NOTE: `count` can be < 1 when cell area is less than pixel area --@ availability: 4.1.1 CREATE TYPE h3_raster_summary_stats AS ( count double precision, sum double precision, mean double precision, stddev double precision, min double precision, max double precision ); -- ST_SummaryStats result type to h3_raster_summary_stats CREATE OR REPLACE FUNCTION __h3_raster_to_summary_stats(stats summarystats) RETURNS h3_raster_summary_stats AS $$ SELECT ROW( (stats).count, (stats).sum, (stats).mean, (stats).stddev, (stats).min, (stats).max )::h3_raster_summary_stats $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_summary_stats_agg_transfn( s1 h3_raster_summary_stats, s2 h3_raster_summary_stats) RETURNS h3_raster_summary_stats AS $$ WITH total AS ( SELECT (s1).count + (s2).count AS count, (s1).sum + (s2).sum AS sum) SELECT ROW( t.count, t.sum, t.sum / t.count, sqrt( ( -- sum of squared values: (variance + mean squared) * count (((s1).stddev * (s1).stddev + (s1).mean * (s1).mean)) * (s1).count + (((s2).stddev * (s2).stddev + (s2).mean * (s2).mean)) * (s2).count ) / t.count - ((t.sum * t.sum) / (t.count * t.count)) -- mean squared ), least((s1).min, (s2).min), greatest((s1).max, (s2).max) )::h3_raster_summary_stats FROM total AS t $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; --@ availability: 4.1.1 CREATE AGGREGATE h3_raster_summary_stats_agg(h3_raster_summary_stats) ( sfunc = __h3_raster_summary_stats_agg_transfn, stype = h3_raster_summary_stats, parallel = safe ); CREATE OR REPLACE FUNCTION __h3_raster_polygon_summary_clip( rast raster, poly geometry, resolution integer, nband integer) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ SELECT h3, __h3_raster_to_summary_stats(ST_SummaryStats(part, nband, TRUE)) AS stats FROM __h3_raster_polygon_to_cell_parts(rast, poly, resolution, nband); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_summary_clip( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ SELECT __h3_raster_polygon_summary_clip( rast, __h3_raster_to_polygon(rast, nband), resolution, nband); $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_summary_clip(raster, integer, integer) IS 'Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Clips the raster by H3 cell geometries and processes each part separately.'; --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_summary_centroids( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ SELECT h3_lat_lng_to_cell(ST_Transform(geom, 4326), resolution) AS h3, ROW( count(val), sum(val), avg(val), stddev_pop(val), min(val), max(val) )::h3_raster_summary_stats AS stats FROM ST_PixelAsCentroids(rast, nband) GROUP BY 1; $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_summary_centroids(raster, integer, integer) IS 'Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Finds corresponding H3 cell for each pixel, then groups values by H3 index.'; CREATE OR REPLACE FUNCTION __h3_raster_polygon_summary_subpixel( rast raster, poly geometry, resolution integer, nband integer, pixels_per_cell double precision) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ SELECT h3, ROW( pixels_per_cell, -- count val, -- sum val, -- mean 0.0, -- stddev val, -- min val -- max )::h3_raster_summary_stats AS stats FROM __h3_raster_polygon_subpixel_cell_values(rast, poly, resolution, nband) t; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_summary_subpixel( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); pixel_area CONSTANT double precision := __h3_raster_polygon_pixel_area(rast, poly); cell_area CONSTANT double precision := __h3_raster_polygon_centroid_cell_area(poly, resolution); BEGIN RETURN QUERY SELECT (__h3_raster_polygon_summary_subpixel( rast, poly, resolution, nband, cell_area / pixel_area)).*; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_summary_subpixel(raster, integer, integer) IS 'Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Assumes H3 cell is smaller than a pixel. Finds corresponding pixel for each H3 cell in raster.'; --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_summary( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); cell_area CONSTANT double precision := __h3_raster_polygon_centroid_cell_area(poly, resolution); pixel_area CONSTANT double precision := __h3_raster_polygon_pixel_area(rast, poly); pixels_per_cell CONSTANT double precision := cell_area / pixel_area; BEGIN IF pixels_per_cell > 70 AND (ST_Area(ST_Transform(poly, 4326)::geography) / cell_area) > 10000 / (pixels_per_cell - 70) THEN RETURN QUERY SELECT (__h3_raster_polygon_summary_clip( rast, poly, resolution, nband )).*; ELSIF pixels_per_cell > 1 THEN RETURN QUERY SELECT (h3_raster_summary_centroids( rast, resolution, nband )).*; ELSE RETURN QUERY SELECT (__h3_raster_polygon_summary_subpixel( rast, poly, resolution, nband, pixels_per_cell )).*; END IF; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_summary(raster, integer, integer) IS 'Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Attempts to select an appropriate method based on number of pixels per H3 cell.'; --| ## Discrete raster data --| --| For rasters where pixels have discrete values corresponding to different classes --| of land cover or land use, H3 cell data summary can be represented by a JSON object --| with separate fields for each class. First, value, number of pixels and approximate --| area are calculated for each H3 cell and value in a raster, then the stats are --| grouped across multiple rasters by H3 index and value, and after that stats for --| different values in a cell are combined into a single JSON object. --| --| The following example query additionally calculates a fraction of H3 cell pixels --| for each value, using a window function to get a total number of pixels: --| ``` --| WITH --| summary AS ( --| -- get aggregated summary for each H3 index/value pair --| SELECT h3, val, h3_raster_class_summary_item_agg(summary) AS item --| FROM --| rasters, --| h3_raster_class_summary(rast, 8) --| GROUP BY 1, 2), --| summary_total AS ( --| -- add total number of pixels per H3 cell --| SELECT h3, val, item, sum((item).count) OVER (PARTITION BY h3) AS total --| FROM summary) --| SELECT --| h3, --| jsonb_object_agg( --| concat('class_', val::text), --| h3_raster_class_summary_item_to_jsonb(item) -- val, count, area --| || jsonb_build_object('fraction', (item).count / total) -- add fraction value --| ORDER BY val --| ) AS summary --| FROM summary_total --| GROUP BY 1; --| --| h3 | summary --| ----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------- --| 88194e6f3bfffff | {"class_1": {"area": 75855.5748, "count": 46, "value": 1, "fraction": 0.4509}, "class_2": {"area": 92345.9171, "count": 56, "value": 2, "fraction": 0.5490}} --| 88194e6f37fffff | {"class_1": {"area": 255600.3064, "count": 155, "value": 1, "fraction": 0.5}, "class_2": {"area": 255600.3064, "count": 155, "value": 2, "fraction": 0.5}} --| 88194e6f33fffff | {"class_1": {"area": 336402.9840, "count": 204, "value": 1, "fraction": 0.5125}, "class_2": {"area": 319912.6416, "count": 194, "value": 2, "fraction": 0.4874}} --| <...> --| ``` --| --| Area covered by pixels with the most frequent value in each cell: --| ``` --| SELECT DISTINCT ON (h3) --| h3, val, (item).area --| FROM ( --| SELECT --| h3, val, h3_raster_class_summary_item_agg(summary) AS item --| FROM --| rasters, --| h3_raster_class_summary(rast, 8) --| GROUP BY 1, 2 --| ) t --| ORDER BY h3, (item).count DESC; --| --| h3 | val | area --| -----------------+-----+-------------------- --| 88194e6f3bfffff | 5 | 23238.699360251427 --| 88194e6f37fffff | 9 | 60863.26022922993 --| 88194e6f33fffff | 8 | 76355.72646939754 --| <...> --| ``` -- NOTE: `count` can be < 1 when cell area is less than pixel area --@ availability: 4.1.1 CREATE TYPE h3_raster_class_summary_item AS ( val integer, count double precision, area double precision ); --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_class_summary_item_to_jsonb( item h3_raster_class_summary_item) RETURNS jsonb AS $$ SELECT jsonb_build_object( 'value', (item).val, 'count', (item).count, 'area', (item).area ); $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary_item_to_jsonb(h3_raster_class_summary_item) IS 'Convert raster summary to JSONB, example: `{"count": 10, "value": 2, "area": 16490.3423}`'; CREATE OR REPLACE FUNCTION __h3_raster_class_summary_item_agg_transfn( s1 h3_raster_class_summary_item, s2 h3_raster_class_summary_item) RETURNS h3_raster_class_summary_item AS $$ SELECT s1.val, s1.count + s2.count, s1.area + s2.area; $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; --@ availability: 4.1.1 CREATE AGGREGATE h3_raster_class_summary_item_agg(h3_raster_class_summary_item) ( stype = h3_raster_class_summary_item, sfunc = __h3_raster_class_summary_item_agg_transfn, parallel = safe ); CREATE OR REPLACE FUNCTION __h3_raster_class_summary_part( rast raster, nband integer, pixel_area double precision) RETURNS SETOF h3_raster_class_summary_item AS $$ SELECT ROW( value::integer, count::double precision, count * pixel_area )::h3_raster_class_summary_item FROM ST_ValueCount(rast, nband) t; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_class_polygon_summary_clip( rast raster, poly geometry, resolution integer, nband integer, pixel_area double precision) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ WITH summary AS ( SELECT h3, __h3_raster_class_summary_part(part, nband, pixel_area) AS summary FROM __h3_raster_polygon_to_cell_parts(rast, poly, resolution, nband) t) SELECT h3, (summary).val, summary FROM summary; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_class_summary_clip( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); BEGIN RETURN QUERY SELECT (__h3_raster_class_polygon_summary_clip( rast, poly, resolution, nband, __h3_raster_polygon_pixel_area(rast, poly) )).*; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary_clip(raster, integer, integer) IS 'Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Clips the raster by H3 cell geometries and processes each part separately.'; CREATE OR REPLACE FUNCTION __h3_raster_class_summary_centroids( rast raster, resolution integer, nband integer, pixel_area double precision) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ SELECT h3_lat_lng_to_cell(ST_Transform(geom, 4326), resolution) AS h3, val::integer AS val, ROW( val::integer, count(*)::double precision, count(*) * pixel_area )::h3_raster_class_summary_item AS summary FROM ST_PixelAsCentroids(rast, nband) GROUP BY 1, 2; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_class_summary_centroids( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ SELECT __h3_raster_class_summary_centroids( rast, resolution, nband, __h3_raster_polygon_pixel_area(rast, __h3_raster_to_polygon(rast, nband)) ); $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary_centroids(raster, integer, integer) IS 'Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Finds corresponding H3 cell for each pixel, then groups by H3 and value.'; CREATE OR REPLACE FUNCTION __h3_raster_class_polygon_summary_subpixel( rast raster, poly geometry, resolution integer, nband integer, cell_area double precision, pixel_area double precision) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ SELECT h3, val::integer AS val, ROW( val::integer, cell_area / pixel_area, cell_area )::h3_raster_class_summary_item AS summary FROM __h3_raster_polygon_subpixel_cell_values(rast, poly, resolution, nband); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_class_summary_subpixel( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); BEGIN RETURN QUERY SELECT (__h3_raster_class_polygon_summary_subpixel( rast, poly, resolution, nband, __h3_raster_polygon_centroid_cell_area(poly, resolution), __h3_raster_polygon_pixel_area(rast, poly) )).*; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary_subpixel(raster, integer, integer) IS 'Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Assumes H3 cell is smaller than a pixel. Finds corresponding pixel for each H3 cell in raster.'; --@ availability: 4.1.1 CREATE OR REPLACE FUNCTION h3_raster_class_summary( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); cell_area CONSTANT double precision := __h3_raster_polygon_centroid_cell_area(poly, resolution); pixel_area CONSTANT double precision := __h3_raster_polygon_pixel_area(rast, poly); pixels_per_cell CONSTANT double precision := cell_area / pixel_area; BEGIN IF pixels_per_cell > 70 AND (ST_Area(ST_Transform(poly, 4326)::geography) / cell_area) > 10000 / (pixels_per_cell - 70) THEN RETURN QUERY SELECT (__h3_raster_class_polygon_summary_clip( rast, poly, resolution, nband, pixel_area )).*; ELSIF pixels_per_cell > 1 THEN RETURN QUERY SELECT (__h3_raster_class_summary_centroids( rast, resolution, nband, pixel_area )).*; ELSE RETURN QUERY SELECT (__h3_raster_class_polygon_summary_subpixel( rast, poly, resolution, nband, cell_area, pixel_area )).*; END IF; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary(raster, integer, integer) IS 'Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Attempts to select an appropriate method based on number of pixels per H3 cell.'; h3-pg-4.2.2/h3_postgis/sql/install/99-deprecated.sql000066400000000000000000000022311475234715600220600ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ --@ availability: 4.0.0 --@ deprecated CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geometry(h3index, extend_antimeridian boolean) RETURNS geometry AS $$ SELECT ST_SetSRID(h3_cell_to_boundary($1, extend_antimeridian)::geometry, 4326) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; --@ availability: 4.0.0 --@ deprecated CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geography(h3index, extend_antimeridian boolean) RETURNS geography AS $$ SELECT ST_SetSRID(h3_cell_to_boundary($1, extend_antimeridian)::geometry, 4326) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; h3-pg-4.2.2/h3_postgis/sql/updates/000077500000000000000000000000001475234715600170015ustar00rootroot00000000000000h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.0.0--4.0.1.sql000066400000000000000000000050031475234715600230270ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.0.1'" to load this file. \quit -- deprecated DROP FUNCTION IF EXISTS h3_cell_to_boundary_geometry(h3index, boolean); CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geometry(h3index, extend_antimeridian boolean) RETURNS geometry AS $$ SELECT ST_SetSRID(h3_cell_to_boundary($1, extend_antimeridian)::geometry, 4326) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; DROP FUNCTION IF EXISTS h3_cell_to_boundary_geography(h3index, boolean); CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geography(h3index, extend_antimeridian boolean) RETURNS geography AS $$ SELECT ST_SetSRID(h3_cell_to_boundary($1, extend_antimeridian)::geometry, 4326) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; -- new splitted version CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geometry(h3index) RETURNS geometry AS $$ SELECT h3_cell_to_boundary_wkb($1)::geometry $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geography(h3index) RETURNS geography AS $$ SELECT h3_cell_to_boundary_wkb($1)::geography $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; -- comments COMMENT ON FUNCTION h3_lat_lng_to_cell(geometry, resolution integer) IS 'Indexes the location at the specified resolution.'; COMMENT ON FUNCTION h3_lat_lng_to_cell(geometry, resolution integer) IS 'Indexes the location at the specified resolution.'; COMMENT ON FUNCTION h3_cell_to_geometry(h3index) IS 'Finds the centroid of the index.'; COMMENT ON FUNCTION h3_cell_to_geography(h3index) IS 'Finds the centroid of the index.'; COMMENT ON FUNCTION h3_cell_to_boundary_geometry(h3index) IS 'Finds the boundary of the index. Splits polygons when crossing 180th meridian.'; COMMENT ON FUNCTION h3_cell_to_boundary_geography(h3index) IS 'Finds the boundary of the index. Splits polygons when crossing 180th meridian.'; h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.0.0.sql000066400000000000000000000062771475234715600223320ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION h3_postgis" to load this file. \quit CREATE OR REPLACE FUNCTION h3_lat_lng_to_cell(geometry, resolution integer) RETURNS h3index AS $$ SELECT h3_lat_lng_to_cell($1::point, $2); $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_lat_lng_to_cell(geography, resolution integer) RETURNS h3index AS $$ SELECT h3_lat_lng_to_cell($1::geometry, $2); $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_cell_to_geometry(h3index) RETURNS geometry AS $$ SELECT ST_SetSRID(h3_cell_to_lat_lng($1)::geometry, 4326) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_cell_to_geography(h3index) RETURNS geography AS $$ SELECT h3_cell_to_geometry($1)::geography $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geometry(h3index, extend boolean DEFAULT FALSE) RETURNS geometry AS $$ SELECT ST_SetSRID(h3_cell_to_boundary($1, $2)::geometry, 4326) $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_cell_to_boundary_geography(h3index, extend boolean DEFAULT FALSE) RETURNS geography AS $$ SELECT h3_cell_to_boundary_geometry($1, $2)::geography $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_polygon_to_cells(multi geometry, resolution integer) RETURNS SETOF h3index AS $$ SELECT h3_polygon_to_cells(exterior, holes, resolution) FROM ( SELECT -- extract exterior ring of each polygon ST_MakePolygon(ST_ExteriorRing(poly))::polygon exterior, -- extract holes of each polygon (SELECT array_agg(hole) FROM ( SELECT ST_MakePolygon(ST_InteriorRingN( poly, generate_series(1, ST_NumInteriorRings(poly)) ))::polygon AS hole ) q_hole ) holes -- extract single polygons from multipolygon FROM ( select (st_dump(multi)).geom as poly ) q_poly GROUP BY poly ) h3_polygon_to_cells; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT CREATE OR REPLACE FUNCTION h3_polygon_to_cells(multi geography, resolution integer) RETURNS SETOF h3index AS $$ SELECT h3_polygon_to_cells($1::geometry, $2) $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT CREATE CAST (h3index AS geometry) WITH FUNCTION h3_cell_to_geometry(h3index); CREATE CAST (h3index AS geography) WITH FUNCTION h3_cell_to_geography(h3index); h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.0.1--4.0.2.sql000066400000000000000000000014061475234715600230340ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.0.2'" to load this file. \quit -- no changes h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.0.2--4.0.3.sql000066400000000000000000000056771475234715600230540ustar00rootroot00000000000000/* * Copyright 2022 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.0.3'" to load this file. \quit CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon_geometry(h3index[]) RETURNS geometry AS $$ SELECT h3_cells_to_multi_polygon_wkb($1)::geometry $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon_geography(h3index[]) RETURNS geography AS $$ SELECT h3_cells_to_multi_polygon_wkb($1)::geography $$ IMMUTABLE STRICT PARALLEL SAFE LANGUAGE SQL; CREATE AGGREGATE h3_cells_to_multi_polygon_geometry(h3index) ( sfunc = array_append, stype = h3index[], finalfunc = h3_cells_to_multi_polygon_geometry, parallel = safe ); CREATE AGGREGATE h3_cells_to_multi_polygon_geography(h3index) ( sfunc = array_append, stype = h3index[], finalfunc = h3_cells_to_multi_polygon_geography, parallel = safe ); CREATE OR REPLACE FUNCTION h3_grid_path_cells_recursive(origin h3index, destination h3index) RETURNS SETOF h3index AS $$ BEGIN IF (SELECT origin != destination AND NOT h3_are_neighbor_cells(origin, destination) AND ((base1 != base2 AND NOT h3_are_neighbor_cells(base1, base2)) OR ((h3_is_pentagon(base1) OR h3_is_pentagon(base2)) AND NOT ( h3_get_icosahedron_faces(origin) && h3_get_icosahedron_faces(destination)))) FROM ( SELECT h3_cell_to_parent(origin, 0) AS base1, h3_cell_to_parent(destination, 0) AS base2) AS t) THEN RETURN QUERY WITH points AS ( SELECT h3_cell_to_geometry(origin) AS g1, h3_cell_to_geometry(destination) AS g2), cells AS ( SELECT h3_lat_lng_to_cell( ST_Centroid(ST_MakeLine(g1, g2)::geography), h3_get_resolution(origin)) AS middle FROM points) SELECT h3_grid_path_cells_recursive(origin, middle) FROM cells UNION SELECT h3_grid_path_cells_recursive(middle, destination) FROM cells; ELSE RETURN QUERY SELECT h3_grid_path_cells(origin, destination); END IF; END; $$ LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL SAFE; h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.0.3--4.1.0.sql000066400000000000000000000013671475234715600230430ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.1.0'" to load this file. \quit h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.1.0--4.1.1.sql000066400000000000000000000475751475234715600230550ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.1.1'" to load this file. \quit -- raster CREATE OR REPLACE FUNCTION h3_cell_to_boundary_wkb(cell h3index) RETURNS bytea AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary_wkb(h3index) IS 'Finds the boundary of the index, converts to EWKB. Splits polygons when crossing 180th meridian. This function has to return WKB since Postgres does not provide multipolygon type.'; CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon_wkb(h3index[]) RETURNS bytea AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cells_to_multi_polygon_wkb(h3index[]) IS 'Create a LinkedGeoPolygon describing the outline(s) of a set of hexagons, converts to EWKB. Splits polygons when crossing 180th meridian.'; -- Raster processing CREATE OR REPLACE FUNCTION __h3_raster_band_nodata( rast raster, nband integer) RETURNS double precision AS $$ SELECT coalesce( ST_BandNoDataValue(rast, nband), ST_MinPossibleValue(ST_BandPixelType(rast, nband))); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_to_polygon( rast raster, nband integer) RETURNS geometry AS $$ SELECT ST_MinConvexHull(rast, nband); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_pixel_area( rast raster, poly geometry) RETURNS double precision AS $$ SELECT ST_Area( ST_Transform( ST_PixelAsPolygon( rast, ST_WorldToRasterCoordX(rast, c), ST_WorldToRasterCoordY(rast, c)), 4326)::geography) FROM ST_Centroid(poly) AS c $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_centroid_cell( poly geometry, resolution integer) RETURNS h3index AS $$ DECLARE cell h3index := h3_lat_lng_to_cell(ST_Transform(ST_Centroid(poly), 4326), resolution); BEGIN IF h3_is_pentagon(cell) THEN SELECT h3 INTO cell FROM h3_grid_disk(cell) AS h3 WHERE h3 != cell LIMIT 1; END IF; RETURN cell; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_centroid_cell_area( poly geometry, resolution integer) RETURNS double precision AS $$ SELECT ST_Area( h3_cell_to_boundary_geography( __h3_raster_polygon_centroid_cell(poly, resolution))); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_to_cells( rast raster, poly geometry, resolution integer, buffer double precision) RETURNS SETOF h3index AS $$ DECLARE buffered geometry := poly; BEGIN IF ST_SRID(rast) != 4326 THEN buffered := ST_Transform( ST_Buffer( poly, greatest(ST_PixelWidth(rast), ST_PixelHeight(rast)), 'join=mitre'), 4326); END IF; IF buffer > 0.0 THEN RETURN QUERY SELECT h3_polygon_to_cells( ST_Buffer( buffered::geography, buffer, 'join=mitre'), resolution); ELSE RETURN QUERY SELECT h3_polygon_to_cells(buffered, resolution); END IF; END $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_to_cell_boundaries_intersects( rast raster, poly geometry, resolution integer) RETURNS TABLE (h3 h3index, geom geometry) AS $$ SELECT h3, geom FROM __h3_raster_polygon_to_cells( rast, poly, resolution, h3_get_hexagon_edge_length_avg(resolution, 'm') * 1.3 ) AS h3, ST_Transform(h3_cell_to_boundary_geometry(h3), ST_SRID(rast)) AS geom WHERE ST_Intersects(geom, poly); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_to_cell_coords_centroid( rast raster, poly geometry, resolution integer) RETURNS TABLE (h3 h3index, x integer, y integer) AS $$ WITH geoms AS ( SELECT h3, ST_Transform( h3_cell_to_geometry(h3), ST_SRID(poly) ) AS geom FROM ( SELECT __h3_raster_polygon_to_cells(rast, poly, resolution, 0.0) AS h3 ) t), coords AS ( SELECT h3, ST_WorldToRasterCoordX(rast, geom) AS x, ST_WorldToRasterCoordY(rast, geom) AS y FROM geoms) SELECT h3, x, y FROM coords WHERE x BETWEEN 1 AND ST_Width(rast) AND y BETWEEN 1 AND ST_Height(rast); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_to_cell_parts( rast raster, poly geometry, resolution integer, nband integer) RETURNS TABLE (h3 h3index, part raster) AS $$ DECLARE nodata CONSTANT double precision := __h3_raster_band_nodata(rast, nband); BEGIN RETURN QUERY SELECT c.h3, p AS part FROM __h3_raster_polygon_to_cell_boundaries_intersects(rast, poly, resolution) AS c, ST_Clip(rast, nband, c.geom, nodata, TRUE) AS p WHERE NOT ST_BandIsNoData(p, nband); END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_polygon_subpixel_cell_values( rast raster, poly geometry, resolution integer, nband integer) RETURNS TABLE (h3 h3index, val double precision) AS $$ SELECT h3, ST_Value(rast, nband, x, y) AS val FROM __h3_raster_polygon_to_cell_coords_centroid(rast, poly, resolution); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; -- Raster processing: continuous data CREATE TYPE h3_raster_summary_stats AS ( count double precision, sum double precision, mean double precision, stddev double precision, min double precision, max double precision ); CREATE OR REPLACE FUNCTION __h3_raster_to_summary_stats(stats summarystats) RETURNS h3_raster_summary_stats AS $$ SELECT ROW( (stats).count, (stats).sum, (stats).mean, (stats).stddev, (stats).min, (stats).max )::h3_raster_summary_stats $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_summary_stats_agg_transfn( s1 h3_raster_summary_stats, s2 h3_raster_summary_stats) RETURNS h3_raster_summary_stats AS $$ WITH total AS ( SELECT (s1).count + (s2).count AS count, (s1).sum + (s2).sum AS sum) SELECT ROW( t.count, t.sum, t.sum / t.count, sqrt( ( -- sum of squared values: (variance + mean squared) * count (((s1).stddev * (s1).stddev + (s1).mean * (s1).mean)) * (s1).count + (((s2).stddev * (s2).stddev + (s2).mean * (s2).mean)) * (s2).count ) / t.count - ((t.sum * t.sum) / (t.count * t.count)) -- mean squared ), least((s1).min, (s2).min), greatest((s1).max, (s2).max) )::h3_raster_summary_stats FROM total AS t $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; CREATE AGGREGATE h3_raster_summary_stats_agg(h3_raster_summary_stats) ( sfunc = __h3_raster_summary_stats_agg_transfn, stype = h3_raster_summary_stats, parallel = safe ); CREATE OR REPLACE FUNCTION __h3_raster_polygon_summary_clip( rast raster, poly geometry, resolution integer, nband integer) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ SELECT h3, __h3_raster_to_summary_stats(ST_SummaryStats(part, nband, TRUE)) AS stats FROM __h3_raster_polygon_to_cell_parts(rast, poly, resolution, nband); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_raster_summary_clip( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ SELECT __h3_raster_polygon_summary_clip( rast, __h3_raster_to_polygon(rast, nband), resolution, nband); $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_summary_clip(raster, integer, integer) IS 'Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Clips the raster by H3 cell geometries and processes each part separately.'; CREATE OR REPLACE FUNCTION h3_raster_summary_centroids( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ SELECT h3_lat_lng_to_cell(ST_Transform(geom, 4326), resolution) AS h3, ROW( count(val), sum(val), avg(val), stddev_pop(val), min(val), max(val) )::h3_raster_summary_stats AS stats FROM ST_PixelAsCentroids(rast, nband) GROUP BY 1; $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_summary_centroids(raster, integer, integer) IS 'Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Finds corresponding H3 cell for each pixel, then groups values by H3 index.'; CREATE OR REPLACE FUNCTION __h3_raster_polygon_summary_subpixel( rast raster, poly geometry, resolution integer, nband integer, pixels_per_cell double precision) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ SELECT h3, ROW( pixels_per_cell, -- count val, -- sum val, -- mean 0.0, -- stddev val, -- min val -- max )::h3_raster_summary_stats AS stats FROM __h3_raster_polygon_subpixel_cell_values(rast, poly, resolution, nband) t; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_raster_summary_subpixel( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); pixel_area CONSTANT double precision := __h3_raster_polygon_pixel_area(rast, poly); cell_area CONSTANT double precision := __h3_raster_polygon_centroid_cell_area(poly, resolution); BEGIN RETURN QUERY SELECT (__h3_raster_polygon_summary_subpixel( rast, poly, resolution, nband, cell_area / pixel_area)).*; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_summary_subpixel(raster, integer, integer) IS 'Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Assumes H3 cell is smaller than a pixel. Finds corresponding pixel for each H3 cell in raster.'; CREATE OR REPLACE FUNCTION h3_raster_summary( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, stats h3_raster_summary_stats) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); cell_area CONSTANT double precision := __h3_raster_polygon_centroid_cell_area(poly, resolution); pixel_area CONSTANT double precision := __h3_raster_polygon_pixel_area(rast, poly); pixels_per_cell CONSTANT double precision := cell_area / pixel_area; BEGIN IF pixels_per_cell > 70 AND (ST_Area(ST_Transform(poly, 4326)::geography) / cell_area) > 10000 / (pixels_per_cell - 70) THEN RETURN QUERY SELECT (__h3_raster_polygon_summary_clip( rast, poly, resolution, nband )).*; ELSIF pixels_per_cell > 1 THEN RETURN QUERY SELECT (h3_raster_summary_centroids( rast, resolution, nband )).*; ELSE RETURN QUERY SELECT (__h3_raster_polygon_summary_subpixel( rast, poly, resolution, nband, pixels_per_cell )).*; END IF; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_summary(raster, integer, integer) IS 'Returns `h3_raster_summary_stats` for each H3 cell in raster for a given band. Attempts to select an appropriate method based on number of pixels per H3 cell.'; -- Raster processing: discrete data CREATE TYPE h3_raster_class_summary_item AS ( val integer, count double precision, area double precision ); CREATE OR REPLACE FUNCTION h3_raster_class_summary_item_to_jsonb( item h3_raster_class_summary_item) RETURNS jsonb AS $$ SELECT jsonb_build_object( 'value', (item).val, 'count', (item).count, 'area', (item).area ); $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary_item_to_jsonb(h3_raster_class_summary_item) IS 'Convert raster summary to JSONB, example: `{"count": 10, "value": 2, "area": 16490.3423}`'; CREATE OR REPLACE FUNCTION __h3_raster_class_summary_item_agg_transfn( s1 h3_raster_class_summary_item, s2 h3_raster_class_summary_item) RETURNS h3_raster_class_summary_item AS $$ SELECT s1.val, s1.count + s2.count, s1.area + s2.area; $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; CREATE AGGREGATE h3_raster_class_summary_item_agg(h3_raster_class_summary_item) ( stype = h3_raster_class_summary_item, sfunc = __h3_raster_class_summary_item_agg_transfn, parallel = safe ); CREATE OR REPLACE FUNCTION __h3_raster_class_summary_part( rast raster, nband integer, pixel_area double precision) RETURNS SETOF h3_raster_class_summary_item AS $$ SELECT ROW( value::integer, count::double precision, count * pixel_area )::h3_raster_class_summary_item FROM ST_ValueCount(rast, nband) t; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION __h3_raster_class_polygon_summary_clip( rast raster, poly geometry, resolution integer, nband integer, pixel_area double precision) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ WITH summary AS ( SELECT h3, __h3_raster_class_summary_part(part, nband, pixel_area) AS summary FROM __h3_raster_polygon_to_cell_parts(rast, poly, resolution, nband) t) SELECT h3, (summary).val, summary FROM summary; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_raster_class_summary_clip( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); BEGIN RETURN QUERY SELECT (__h3_raster_class_polygon_summary_clip( rast, poly, resolution, nband, __h3_raster_polygon_pixel_area(rast, poly) )).*; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary_clip(raster, integer, integer) IS 'Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Clips the raster by H3 cell geometries and processes each part separately.'; CREATE OR REPLACE FUNCTION __h3_raster_class_summary_centroids( rast raster, resolution integer, nband integer, pixel_area double precision) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ SELECT h3_lat_lng_to_cell(ST_Transform(geom, 4326), resolution) AS h3, val::integer AS val, ROW( val::integer, count(*)::double precision, count(*) * pixel_area )::h3_raster_class_summary_item AS summary FROM ST_PixelAsCentroids(rast, nband) GROUP BY 1, 2; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_raster_class_summary_centroids( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ SELECT __h3_raster_class_summary_centroids( rast, resolution, nband, __h3_raster_polygon_pixel_area(rast, __h3_raster_to_polygon(rast, nband)) ); $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary_centroids(raster, integer, integer) IS 'Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Finds corresponding H3 cell for each pixel, then groups by H3 and value.'; CREATE OR REPLACE FUNCTION __h3_raster_class_polygon_summary_subpixel( rast raster, poly geometry, resolution integer, nband integer, cell_area double precision, pixel_area double precision) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ SELECT h3, val::integer AS val, ROW( val::integer, cell_area / pixel_area, cell_area )::h3_raster_class_summary_item AS summary FROM __h3_raster_polygon_subpixel_cell_values(rast, poly, resolution, nband); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE OR REPLACE FUNCTION h3_raster_class_summary_subpixel( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); BEGIN RETURN QUERY SELECT (__h3_raster_class_polygon_summary_subpixel( rast, poly, resolution, nband, __h3_raster_polygon_centroid_cell_area(poly, resolution), __h3_raster_polygon_pixel_area(rast, poly) )).*; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary_subpixel(raster, integer, integer) IS 'Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Assumes H3 cell is smaller than a pixel. Finds corresponding pixel for each H3 cell in raster.'; CREATE OR REPLACE FUNCTION h3_raster_class_summary( rast raster, resolution integer, nband integer DEFAULT 1) RETURNS TABLE (h3 h3index, val integer, summary h3_raster_class_summary_item) AS $$ DECLARE poly CONSTANT geometry := __h3_raster_to_polygon(rast, nband); cell_area CONSTANT double precision := __h3_raster_polygon_centroid_cell_area(poly, resolution); pixel_area CONSTANT double precision := __h3_raster_polygon_pixel_area(rast, poly); pixels_per_cell CONSTANT double precision := cell_area / pixel_area; BEGIN IF pixels_per_cell > 70 AND (ST_Area(ST_Transform(poly, 4326)::geography) / cell_area) > 10000 / (pixels_per_cell - 70) THEN RETURN QUERY SELECT (__h3_raster_class_polygon_summary_clip( rast, poly, resolution, nband, pixel_area )).*; ELSIF pixels_per_cell > 1 THEN RETURN QUERY SELECT (__h3_raster_class_summary_centroids( rast, resolution, nband, pixel_area )).*; ELSE RETURN QUERY SELECT (__h3_raster_class_polygon_summary_subpixel( rast, poly, resolution, nband, cell_area, pixel_area )).*; END IF; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_raster_class_summary(raster, integer, integer) IS 'Returns `h3_raster_class_summary_item` for each H3 cell and value for a given band. Attempts to select an appropriate method based on number of pixels per H3 cell.'; h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.1.1--4.1.2.sql000066400000000000000000000027751475234715600230500ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.1.2'" to load this file. \quit -- fix wrong shared lib #117 CREATE OR REPLACE FUNCTION h3_cell_to_boundary_wkb(cell h3index) RETURNS bytea AS 'h3_postgis' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cell_to_boundary_wkb(h3index) IS 'Finds the boundary of the index, converts to EWKB. Splits polygons when crossing 180th meridian. This function has to return WKB since Postgres does not provide multipolygon type.'; CREATE OR REPLACE FUNCTION h3_cells_to_multi_polygon_wkb(h3index[]) RETURNS bytea AS 'h3_postgis' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION h3_cells_to_multi_polygon_wkb(h3index[]) IS 'Create a LinkedGeoPolygon describing the outline(s) of a set of hexagons, converts to EWKB. Splits polygons when crossing 180th meridian.'; h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.1.2--4.1.3.sql000066400000000000000000000022551475234715600230430ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.1.3'" to load this file. \quit --@ availability: 4.1.3 CREATE OPERATOR @ ( PROCEDURE = h3_lat_lng_to_cell, LEFTARG = geometry, RIGHTARG = integer ); COMMENT ON OPERATOR @ (geometry, integer) IS 'Index geometry at specified resolution.'; --@ availability: 4.1.3 CREATE OPERATOR @ ( PROCEDURE = h3_lat_lng_to_cell, LEFTARG = geography, RIGHTARG = integer ); COMMENT ON OPERATOR @ (geography, integer) IS 'Index geography at specified resolution.'; h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.1.3--4.1.4.sql000066400000000000000000000013671475234715600230500ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.1.4'" to load this file. \quit h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.1.4--4.2.0.sql000066400000000000000000000040201475234715600230330ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.2.0'" to load this file. \quit CREATE OR REPLACE FUNCTION h3_polygon_to_cells_experimental(multi geometry, resolution integer, containment_mode text DEFAULT 'center') RETURNS SETOF h3index AS $$ SELECT h3_polygon_to_cells_experimental(exterior, holes, resolution, containment_mode) FROM ( SELECT -- extract exterior ring of each polygon ST_MakePolygon(ST_ExteriorRing(poly))::polygon exterior, -- extract holes of each polygon (SELECT array_agg(hole) FROM ( SELECT ST_MakePolygon(ST_InteriorRingN( poly, generate_series(1, ST_NumInteriorRings(poly)) ))::polygon AS hole ) q_hole ) holes -- extract single polygons from multipolygon FROM ( select (st_dump(multi)).geom as poly ) q_poly GROUP BY poly ) h3_polygon_to_cells; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT CREATE OR REPLACE FUNCTION h3_polygon_to_cells_experimental(multi geography, resolution integer, containment_mode text DEFAULT 'center') RETURNS SETOF h3index AS $$ SELECT h3_polygon_to_cells_experimental($1::geometry, $2, $3) $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE CALLED ON NULL INPUT; -- NOT STRICT h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.2.0--4.2.1.sql000066400000000000000000000013671475234715600230440ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.2.1'" to load this file. \quit h3-pg-4.2.2/h3_postgis/sql/updates/h3_postgis--4.2.1--4.2.2.sql000066400000000000000000000013671475234715600230460ustar00rootroot00000000000000/* * Copyright 2025 Zacharias Knudsen * * 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. */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3_postgis UPDATE TO '4.2.2'" to load this file. \quit h3-pg-4.2.2/h3_postgis/src/000077500000000000000000000000001475234715600153245ustar00rootroot00000000000000h3-pg-4.2.2/h3_postgis/src/init.c000066400000000000000000000013661475234715600164410ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #include #include // PG_MODULE_MAGIC /* see https://www.postgresql.org/docs/current/xfunc-c.html#XFUNC-C-DYNLOAD */ PG_MODULE_MAGIC; h3-pg-4.2.2/h3_postgis/src/wkb.c000066400000000000000000000210361475234715600162550ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include "error.h" #include "wkb.h" #include "wkb_linked_geo.h" #define WKB_BYTE_SIZE 1 #define WKB_INT_SIZE 4 #define WKB_DOUBLE_SIZE 8 #define WKB_NDR 1 #define WKB_XDR 0 #define WKB_POLYGON_TYPE 3 #define WKB_MULTIPOLYGON_TYPE 6 #define WKB_SRID_FLAG 0x20000000 #define WKB_SRID_DEFAULT 4326 #define ASSERT_WKB_DATA_WRITTEN(wkb, data) \ ASSERT( \ (uint8 *)wkb + VARSIZE(wkb) == data, \ ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, \ "# of written bytes (%d) must match allocation size (%d)", \ (int)(data - (uint8 *)wkb), VARSIZE(wkb)) static bool boundary_is_empty(const CellBoundary * boundary); static bool boundary_is_closed(const CellBoundary * boundary); static size_t boundary_array_data_size(const CellBoundary * boundaries, int num); static size_t boundary_data_size(const CellBoundary * boundary); static size_t linked_geo_polygon_data_size(const LinkedGeoPolygon * multiPolygon); static size_t linked_geo_loop_data_size(const LinkedGeoLoop * loop); static uint8 * wkb_write_boundary_array_data(uint8 *data, const CellBoundary * boundaries, int num); static uint8 * wkb_write_boundary_data(uint8 *data, const CellBoundary * boundary); static uint8 * wkb_write_linked_geo_polygon_data(uint8 *data, const LinkedGeoPolygon * multiPolygon); static uint8 * wkb_write_lat_lng_array(uint8 *data, const LatLng * coord, int num); static uint8 * wkb_write_linked_geo_loop_data(uint8 *data, const LinkedGeoLoop * loop); static uint8 * wkb_write_lat_lng(uint8 *data, const LatLng * coord); static uint8 * wkb_write_endian(uint8 *data); static uint8 * wkb_write_int(uint8 *data, uint32 value); static uint8 * wkb_write(uint8 *data, const void *value, size_t size); bytea * boundary_array_to_wkb(const CellBoundary * boundaries, size_t num) { uint8 *data; bytea *wkb; size_t size = boundary_array_data_size(boundaries, num); wkb = palloc(VARHDRSZ + size); SET_VARSIZE(wkb, VARHDRSZ + size); data = (uint8 *) VARDATA(wkb); data = wkb_write_boundary_array_data(data, boundaries, num); ASSERT_WKB_DATA_WRITTEN(wkb, data); return wkb; } bytea * boundary_to_wkb(const CellBoundary * boundary) { bytea *wkb; uint8 *data; size_t size = boundary_data_size(boundary); wkb = palloc(VARHDRSZ + size); SET_VARSIZE(wkb, VARHDRSZ + size); data = (uint8 *) VARDATA(wkb); data = wkb_write_boundary_data(data, boundary); ASSERT_WKB_DATA_WRITTEN(wkb, data); return wkb; } bytea * linked_geo_polygon_to_wkb(const LinkedGeoPolygon * multiPolygon) { bytea *wkb; uint8 *data; size_t size = linked_geo_polygon_data_size(multiPolygon); wkb = palloc(VARHDRSZ + size); SET_VARSIZE(wkb, VARHDRSZ + size); data = (uint8 *) VARDATA(wkb); data = wkb_write_linked_geo_polygon_data(data, multiPolygon); ASSERT_WKB_DATA_WRITTEN(wkb, data); return wkb; } bool boundary_is_empty(const CellBoundary * boundary) { return boundary->numVerts < 1; } bool boundary_is_closed(const CellBoundary * boundary) { const LatLng *verts; int numVerts; if (boundary_is_empty(boundary)) return true; verts = boundary->verts; numVerts = boundary->numVerts; return verts[0].lng == verts[numVerts - 1].lng && verts[1].lat == verts[numVerts - 1].lat; } size_t boundary_array_data_size(const CellBoundary * boundaries, int num) { /* byte order + type + srid + # of polygons */ size_t size = WKB_BYTE_SIZE + WKB_INT_SIZE * 3; /* boundaries */ for (int i = 0; i < num; i++) size += boundary_data_size(&boundaries[i]); return size; } size_t boundary_data_size(const CellBoundary * boundary) { /* byte order + type + srid + # of rings */ size_t size = WKB_BYTE_SIZE + WKB_INT_SIZE * 3; /* points */ if (!boundary_is_empty(boundary)) { int numVerts = boundary->numVerts; if (!boundary_is_closed(boundary)) numVerts++; /* # of points, point data */ size += WKB_INT_SIZE + numVerts * WKB_DOUBLE_SIZE * 2; } return size; } size_t linked_geo_polygon_data_size(const LinkedGeoPolygon * multiPolygon) { size_t size = 0; int isMulti = (multiPolygon->next != NULL); /* byte order + type + srid */ size = WKB_BYTE_SIZE + WKB_INT_SIZE * 2; if (isMulti) { /* # of polygons */ size += WKB_INT_SIZE; } FOREACH_LINKED_POLYGON(multiPolygon, polygon) { if (isMulti) { /* byte order + type + srid */ size += WKB_BYTE_SIZE + WKB_INT_SIZE * 2; } /* # of rings */ size += WKB_INT_SIZE; FOREACH_LINKED_LOOP(polygon, loop) { size += linked_geo_loop_data_size(loop); } } return size; } size_t linked_geo_loop_data_size(const LinkedGeoLoop * loop) { /* ring size */ size_t size = WKB_INT_SIZE; /* point data (including closing point) */ size += WKB_DOUBLE_SIZE * (count_linked_lat_lng(loop) + 1) * 2; return size; } uint8 * wkb_write_boundary_array_data(uint8 *data, const CellBoundary * boundaries, int num) { /* byte order */ data = wkb_write_endian(data); /* type */ data = wkb_write_int(data, WKB_MULTIPOLYGON_TYPE | WKB_SRID_FLAG); /* SRID */ data = wkb_write_int(data, WKB_SRID_DEFAULT); /* # of polygons */ data = wkb_write_int(data, num); /* polygons */ for (int i = 0; i < num; i++) data = wkb_write_boundary_data(data, &boundaries[i]); return data; } uint8 * wkb_write_boundary_data(uint8 *data, const CellBoundary * boundary) { /* byte order */ data = wkb_write_endian(data); /* type */ data = wkb_write_int(data, WKB_POLYGON_TYPE | WKB_SRID_FLAG); /* SRID */ data = wkb_write_int(data, WKB_SRID_DEFAULT); /* # of rings */ data = wkb_write_int(data, boundary_is_empty(boundary) ? 0 : 1); /* exterior ring */ if (!boundary_is_empty(boundary)) { bool is_closed = boundary_is_closed(boundary); data = wkb_write_int(data, boundary->numVerts + (is_closed ? 0 : 1)); data = wkb_write_lat_lng_array(data, boundary->verts, boundary->numVerts); /* close the ring */ if (!is_closed) data = wkb_write_lat_lng(data, &boundary->verts[0]); } return data; } uint8 * wkb_write_linked_geo_polygon_data(uint8 *data, const LinkedGeoPolygon * multiPolygon) { int isMulti = (multiPolygon->next != NULL); int type = isMulti ? WKB_MULTIPOLYGON_TYPE : WKB_POLYGON_TYPE; /* byte order */ data = wkb_write_endian(data); /* type */ data = wkb_write_int(data, type | WKB_SRID_FLAG); /* SRID */ data = wkb_write_int(data, WKB_SRID_DEFAULT); if (isMulti) { /* # of polygons */ data = wkb_write_int(data, count_linked_polygons(multiPolygon)); } FOREACH_LINKED_POLYGON(multiPolygon, polygon) { if (isMulti) { /* byte order */ data = wkb_write_endian(data); /* type */ data = wkb_write_int(data, WKB_POLYGON_TYPE | WKB_SRID_FLAG); /* SRID */ data = wkb_write_int(data, WKB_SRID_DEFAULT); } /* # of rings */ data = wkb_write_int(data, count_linked_geo_loops(polygon)); /* rings */ FOREACH_LINKED_LOOP(polygon, loop) { data = wkb_write_linked_geo_loop_data(data, loop); } } return data; } uint8 * wkb_write_lat_lng_array(uint8 *data, const LatLng * coords, int num) { for (int i = 0; i < num; i++) data = wkb_write_lat_lng(data, &coords[i]); return data; } uint8 * wkb_write_linked_geo_loop_data(uint8 *data, const LinkedGeoLoop * loop) { /* # of points (including closing point) */ data = wkb_write_int(data, count_linked_lat_lng(loop) + 1); /* point data */ FOREACH_LINKED_LAT_LNG(loop, latlng) { data = wkb_write_lat_lng(data, &latlng->vertex); } /* closing point data */ data = wkb_write_lat_lng(data, &loop->first->vertex); return data; } uint8 * wkb_write_lat_lng(uint8 *data, const LatLng * coord) { data = wkb_write(data, &coord->lng, sizeof(coord->lng)); data = wkb_write(data, &coord->lat, sizeof(coord->lat)); return data; } uint8 * wkb_write_endian(uint8 *data) { /* Always use native order */ uint32 order = 0x00000001; data[0] = ((uint8 *) &order)[0] ? WKB_NDR : WKB_XDR; return data + 1; } uint8 * wkb_write_int(uint8 *data, uint32 value) { return wkb_write(data, &value, sizeof(value)); } uint8 * wkb_write(uint8 *data, const void *value, size_t size) { memcpy(data, value, size); return data + size; } h3-pg-4.2.2/h3_postgis/src/wkb.h000066400000000000000000000020221475234715600162540ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #ifndef PGH3_WKB_H #define PGH3_WKB_H #include #include #include #if POSTGRESQL_VERSION_MAJOR >= 16 #include "varatt.h" //VAR_SIZE and friends moved to here from postgres.h #endif bytea * boundary_array_to_wkb(const CellBoundary * boundaries, size_t num); bytea * boundary_to_wkb(const CellBoundary * boundary); bytea * linked_geo_polygon_to_wkb(const LinkedGeoPolygon * multiPolygon); #endif h3-pg-4.2.2/h3_postgis/src/wkb_bbox3.c000066400000000000000000000114341475234715600173530ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include "wkb_bbox3.h" #include "wkb_linked_geo.h" typedef struct { double x; double y; } Vect2; static void vect2_normalize(Vect2 * vect); static short vect2_segment_side(const Vect2 * start, const Vect2 * end, const Vect2 * point); static void bbox3_merge_vect3(const Vect3 * vect, Bbox3 * bbox); void bbox3_from_vect3(const Vect3 * vect, Bbox3 * bbox) { bbox->xmin = bbox->xmax = vect->x; bbox->ymin = bbox->ymax = vect->y; bbox->zmin = bbox->zmax = vect->z; } void bbox3_merge(const Bbox3 * other, Bbox3 * bbox) { if (other->xmin < bbox->xmin) bbox->xmin = other->xmin; if (other->xmax > bbox->xmax) bbox->xmax = other->xmax; if (other->ymin < bbox->ymin) bbox->ymin = other->ymin; if (other->ymax > bbox->ymax) bbox->ymax = other->ymax; if (other->zmin < bbox->zmin) bbox->zmin = other->zmin; if (other->zmax > bbox->zmax) bbox->zmax = other->zmax; } void bbox3_from_linked_loop(const LinkedGeoLoop * loop, Bbox3 * bbox) { Vect3 vect, nextVect; vect3_from_lat_lng(&loop->first->vertex, &vect); bbox3_from_vect3(&vect, bbox); if (!loop->first->next) return; FOREACH_LINKED_LAT_LNG_PAIR(loop, cur, next) { Bbox3 segmentBbox; vect3_from_lat_lng(&next->vertex, &nextVect); if (!vect3_eq(&vect, &nextVect)) { bbox3_from_segment_vect3(&vect, &nextVect, &segmentBbox); bbox3_merge(&segmentBbox, bbox); } vect = nextVect; } } int bbox3_contains_vect3(const Bbox3 * bbox, const Vect3 * vect) { return bbox->xmin <= vect->x && vect->x <= bbox->xmax && bbox->ymin <= vect->y && vect->y <= bbox->ymax && bbox->zmin <= vect->z && vect->z <= bbox->zmax; } int bbox3_contains_lat_lng(const Bbox3 * bbox, const LatLng * coord) { Vect3 vect; vect3_from_lat_lng(coord, &vect); return bbox3_contains_vect3(bbox, &vect); } void bbox3_from_segment_vect3(const Vect3 * vect1, const Vect3 * vect2, Bbox3 * bbox) { Vect3 vn, vect3; Vect3 axes[6]; Vect2 r1, r2, orig; short orig_side; /* Init bbox */ bbox3_from_vect3(vect1, bbox); bbox3_merge_vect3(vect2, bbox); /* Check if end points are the same */ if (vect3_eq(vect1, vect2)) return; /* Normal of plain containing endpoint vectors */ vect3_cross(vect1, vect2, &vn); vect3_normalize(&vn); /* Vector orthogonal to first endpoint vector */ vect3_cross(&vn, vect1, &vect3); /* Project endpoint vectors onto the plane, using vect1/vect3 as basis */ r1.x = 1.0; r1.y = 0.0; r2.x = vect3_dot(vect2, vect1); r2.y = vect3_dot(vect2, &vect3); /* Origin side relative to the projected segment (r1, r2) */ orig.x = 0.0; orig.y = 0.0; orig_side = vect2_segment_side(&r1, &r2, &orig); /* Axis points: (1, 0, 0), (-1, 0, 0), (0, 1, 0), ... */ memset(axes, 0, 6 * sizeof(Vect3)); axes[0].x = axes[2].y = axes[4].z = 1.0; axes[1].x = axes[3].y = axes[5].z = -1.0; for (int i = 0; i < 6; ++i) { /* Project axis onto the plane, normalize */ Vect2 rx; rx.x = vect3_dot(&axes[i], vect1); rx.y = vect3_dot(&axes[i], &vect3); vect2_normalize(&rx); /* Is projected axis vector between r1 and r2 */ /* (is origin on the opposite side of segment (r1, r2))? */ if (vect2_segment_side(&r1, &r2, &rx) != orig_side) { Vect3 vx; vx.x = rx.x * vect1->x + rx.y * vect3.x; vx.y = rx.x * vect1->y + rx.y * vect3.y; vx.z = rx.x * vect1->z + rx.y * vect3.z; bbox3_merge_vect3(&vx, bbox); } } } void bbox3_from_segment_lat_lng(const LatLng * coord1, const LatLng * coord2, Bbox3 * bbox) { Vect3 vect1, vect2; vect3_from_lat_lng(coord1, &vect1); vect3_from_lat_lng(coord2, &vect2); bbox3_from_segment_vect3(&vect1, &vect2, bbox); } static void vect2_normalize(Vect2 * vect) { double len = sqrt(vect->x * vect->x + vect->y * vect->y); if (len > 0.0) { vect->x /= len; vect->y /= len; } else { vect->x = 0.0; vect->y = 0.0; } } static short vect2_segment_side(const Vect2 * start, const Vect2 * end, const Vect2 * point) { double side = (point->x - start->x) * (end->y - start->y) - (end->x - start->x) * (point->y - start->y); return (side == 0) ? 0 : (side < 0) ? -1 : 1; } static void bbox3_merge_vect3(const Vect3 * vect, Bbox3 * bbox) { Bbox3 vect_bbox; bbox3_from_vect3(vect, &vect_bbox); bbox3_merge(&vect_bbox, bbox); } h3-pg-4.2.2/h3_postgis/src/wkb_bbox3.h000066400000000000000000000025011475234715600173530ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #ifndef PGH3_WKB_BBOX3_H #define PGH3_WKB_BBOX3_H #include #include "wkb_vect3.h" typedef struct { double xmin; double xmax; double ymin; double ymax; double zmin; double zmax; } Bbox3; void bbox3_from_vect3(const Vect3 * vect, Bbox3 * bbox); void bbox3_merge(const Bbox3 * other, Bbox3 * bbox); void bbox3_from_linked_loop(const LinkedGeoLoop * loop, Bbox3 * bbox); int bbox3_contains_vect3(const Bbox3 * bbox, const Vect3 * vect); int bbox3_contains_lat_lng(const Bbox3 * bbox, const LatLng * coord); void bbox3_from_segment_vect3(const Vect3 * vect1, const Vect3 * vect2, Bbox3 * bbox); void bbox3_from_segment_lat_lng(const LatLng * coord1, const LatLng * coord2, Bbox3 * bbox); #endif h3-pg-4.2.2/h3_postgis/src/wkb_indexing.c000066400000000000000000000132361475234715600201450ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include #include "constants.h" #include "error.h" #include "type.h" #include "wkb_split.h" #include "wkb_vect3.h" #include "wkb.h" #define SIGN(x) ((x < 0) ? -1 : (x > 0) ? 1 \ : 0) #define ABS_LAT_MAX (degsToRads(89.9999)) #define SPLIT_ASSERT(condition, message) \ ASSERT( \ condition, \ ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, \ message) PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cell_to_boundary_wkb); /* Converts CellBoundary coordinates to degrees in place */ static void boundary_to_degs(CellBoundary * boundary); /* Checks if CellBoundary is crossed by antimeridian */ static int boundary_crosses_180_num(const CellBoundary * boundary); /* Splits CellBoundary by antimeridian (and 0 meridian around poles) */ static void boundary_split_180(const CellBoundary * boundary, CellBoundary * left, CellBoundary * right); /* Creates a boundary for polar cells with additional points on an antimeridian. The functions adds 2 points (with lon. 180 and -180) for intersection with antimeridian and 2 points on antimeridian close to the pole. This allows to better display polar cells in e.g. Mercator projection. */ static void boundary_split_180_polar(const CellBoundary * boundary, CellBoundary * res); /* Finds the boundary of the index, converts to EWKB, splits the boundary by 180 meridian */ Datum h3_cell_to_boundary_wkb(PG_FUNCTION_ARGS) { H3Index cell = PG_GETARG_H3INDEX(0); bytea *wkb; CellBoundary boundary; int crossNum; h3_assert(cellToBoundary(cell, &boundary)); crossNum = boundary_crosses_180_num(&boundary); if (crossNum == 0) { /* Cell is not crossed by antimeridian */ boundary_to_degs(&boundary); wkb = boundary_to_wkb(&boundary); } else if (crossNum == 1) { /* Cell boundary is crossed by antimeridian once */ CellBoundary split; boundary_split_180_polar(&boundary, &split); boundary_to_degs(&split); wkb = boundary_to_wkb(&split); } else { /* Crossed by antimeridian */ CellBoundary parts[2]; boundary_split_180(&boundary, &parts[0], &parts[1]); boundary_to_degs(&parts[0]); boundary_to_degs(&parts[1]); wkb = boundary_array_to_wkb(parts, 2); } PG_RETURN_BYTEA_P(wkb); } void boundary_to_degs(CellBoundary * boundary) { LatLng *verts = boundary->verts; const int numVerts = boundary->numVerts; for (int v = 0; v < numVerts; v++) { verts[v].lng = radsToDegs(verts[v].lng); verts[v].lat = radsToDegs(verts[v].lat); } } int boundary_crosses_180_num(const CellBoundary * boundary) { const int numVerts = boundary->numVerts; const LatLng *verts = boundary->verts; int num = 0; for (int v = 0; v < numVerts; v++) { double lon = verts[v].lng; double nextLon = verts[(v + 1) % numVerts].lng; if (SIGN(lon) != SIGN(nextLon) && fabs(lon - nextLon) > M_PI) { ++num; } } return num; } void boundary_split_180(const CellBoundary * boundary, CellBoundary * part1, CellBoundary * part2) { const int numVerts = boundary->numVerts; const LatLng *verts = boundary->verts; part1->numVerts = 0; part2->numVerts = 0; for (int v = 0; v < numVerts; v++) { int next = (v + 1) % numVerts; double lon; double nextLon; CellBoundary *part; lon = verts[v].lng; nextLon = verts[next].lng; part = (lon < 0) ? part1 : part2; /* Add current vertex */ part->verts[part->numVerts++] = verts[v]; if (SIGN(lon) != SIGN(nextLon)) { LatLng vert; SPLIT_ASSERT( fabs(lon - nextLon) > M_PI, "Cell boundaries crossed by the Prime meridian " "must be handled in `boundary_split_180_polar`"); vert.lat = split_180_lat(&verts[v], &verts[next]); vert.lng = (lon < 0) ? -M_PI : M_PI; /* Add split point */ /* current part */ part->verts[part->numVerts++] = vert; /* next part */ vert.lng = -vert.lng; part = (part == part1) ? part2 : part1; part->verts[part->numVerts++] = vert; } } } void boundary_split_180_polar(const CellBoundary * boundary, CellBoundary * res) { const int numVerts = boundary->numVerts; const LatLng *verts = boundary->verts; res->numVerts = 0; for (int v = 0; v < numVerts; v++) { int next = (v + 1) % numVerts; double lon; double nextLon; /* Add current vertex */ res->verts[res->numVerts++] = verts[v]; lon = verts[v].lng; nextLon = verts[next].lng; if (SIGN(lon) != SIGN(nextLon) && fabs(lon - nextLon) > M_PI) { LatLng vert; double splitLat; SPLIT_ASSERT( v + 1 == res->numVerts, "Cell boundaries crossed by antimeridian more than once " "must be handled in `boundary_split_180`"); splitLat = split_180_lat(&verts[v], &verts[next]); /* Add intersection point */ vert.lat = splitLat; vert.lng = (lon < 0) ? -M_PI : M_PI; res->verts[res->numVerts++] = vert; /* Add points on antimeridian near the pole */ vert.lat = SIGN(vert.lat) * ABS_LAT_MAX; res->verts[res->numVerts++] = vert; vert.lng = -vert.lng; res->verts[res->numVerts++] = vert; /* Add intersection point */ vert.lat = splitLat; res->verts[res->numVerts++] = vert; } } } h3-pg-4.2.2/h3_postgis/src/wkb_linked_geo.c000066400000000000000000000055101475234715600204340ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include "wkb_linked_geo.h" static void free_linked_geo_loop(LinkedGeoLoop * loop); int count_linked_polygons(const LinkedGeoPolygon * multiPolygon) { int num = 0; FOREACH_LINKED_POLYGON(multiPolygon, _) ++ num; return num; } int count_linked_geo_loops(const LinkedGeoPolygon * polygon) { int num = 0; FOREACH_LINKED_LOOP(polygon, _) ++ num; return num; } int count_linked_lat_lng(const LinkedGeoLoop * loop) { int num = 0; FOREACH_LINKED_LAT_LNG(loop, _) ++ num; return num; } LinkedGeoPolygon * copy_linked_geo_polygon(const LinkedGeoPolygon * polygon) { LinkedGeoPolygon *copy = palloc0(sizeof(LinkedGeoPolygon)); FOREACH_LINKED_LOOP(polygon, loop) { LinkedGeoLoop *loopCopy = copy_linked_geo_loop(loop); add_linked_geo_loop(copy, loopCopy); } return copy; } LinkedGeoLoop * copy_linked_geo_loop(const LinkedGeoLoop * loop) { LinkedGeoLoop *copy = palloc0(sizeof(LinkedGeoLoop)); FOREACH_LINKED_LAT_LNG(loop, latlng) { LinkedLatLng *latlng_copy = copy_linked_lat_lng(latlng); add_linked_lat_lng(copy, latlng_copy); } return copy; } LinkedLatLng * copy_linked_lat_lng(const LinkedLatLng * latlng) { LinkedLatLng *copy = palloc0(sizeof(LinkedLatLng)); copy->vertex = latlng->vertex; return copy; } void add_linked_geo_loop(LinkedGeoPolygon * polygon, LinkedGeoLoop * loop) { LinkedGeoLoop *last = polygon->last; if (!last) polygon->first = loop; else last->next = loop; polygon->last = loop; } void add_linked_lat_lng(LinkedGeoLoop * loop, LinkedLatLng * latlng) { LinkedLatLng *last = loop->last; if (!last) loop->first = latlng; else last->next = latlng; loop->last = latlng; } void free_linked_geo_polygon(LinkedGeoPolygon * multiPolygon) { LinkedGeoPolygon *polygon = multiPolygon; while (polygon) { LinkedGeoPolygon *nextPolygon = polygon->next; /* Free loops */ LinkedGeoLoop *loop = polygon->first; while (loop) { LinkedGeoLoop *nextLoop = loop->next; free_linked_geo_loop(loop); loop = nextLoop; } pfree(polygon); polygon = nextPolygon; } } void free_linked_geo_loop(LinkedGeoLoop * loop) { LinkedLatLng *latlng = loop->first; while (latlng) { LinkedLatLng *next = latlng->next; pfree(latlng); latlng = next; } pfree(loop); } h3-pg-4.2.2/h3_postgis/src/wkb_linked_geo.h000066400000000000000000000046251475234715600204470ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #ifndef PGH3_WKB_LINKED_GEO_H #define PGH3_WKB_LINKED_GEO_H #include #define _FOREACH_LINKED_ITEM(first, item, Type) \ for (Type *item = (first); \ item != NULL; \ item = item->next) #define FOREACH_LINKED_POLYGON(multiPolygon, polygon) \ _FOREACH_LINKED_ITEM(multiPolygon, polygon, const LinkedGeoPolygon) #define FOREACH_LINKED_POLYGON_NOCONST(multiPolygon, polygon) \ _FOREACH_LINKED_ITEM(multiPolygon, polygon, LinkedGeoPolygon) #define FOREACH_LINKED_LOOP(polygon, loop) \ _FOREACH_LINKED_ITEM(polygon->first, loop, const LinkedGeoLoop) #define FOREACH_LINKED_LOOP_NOCONST(polygon, loop) \ _FOREACH_LINKED_ITEM(polygon->first, loop, LinkedGeoLoop) #define FOREACH_LINKED_LAT_LNG(loop, latlng) \ _FOREACH_LINKED_ITEM(loop->first, latlng, const LinkedLatLng) #define FOREACH_LINKED_LAT_LNG_NOCONST(loop, latlng) \ _FOREACH_LINKED_ITEM(loop->first, latlng, LinkedLatLng) /* NOTE: loop must contain at least 2 points */ #define FOREACH_LINKED_LAT_LNG_PAIR(loop, cur, next) \ for (const LinkedLatLng *cur = loop->first, *next = cur->next; \ cur != NULL; \ cur = cur->next, next = next->next ? next->next : loop->first) int count_linked_polygons(const LinkedGeoPolygon * multiPolygon); int count_linked_geo_loops(const LinkedGeoPolygon * polygon); int count_linked_lat_lng(const LinkedGeoLoop * loop); LinkedGeoPolygon * copy_linked_geo_polygon(const LinkedGeoPolygon * polygon); LinkedGeoLoop * copy_linked_geo_loop(const LinkedGeoLoop * loop); LinkedLatLng * copy_linked_lat_lng(const LinkedLatLng * latlng); void add_linked_geo_loop(LinkedGeoPolygon * polygon, LinkedGeoLoop * loop); void add_linked_lat_lng(LinkedGeoLoop * loop, LinkedLatLng * latlng); void free_linked_geo_polygon(LinkedGeoPolygon * multiPolygon); #endif h3-pg-4.2.2/h3_postgis/src/wkb_regions.c000066400000000000000000000051621475234715600200050ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include // PG_FUNCTION_ARGS #include // using arrays #include "error.h" #include "type.h" #include "wkb_linked_geo.h" #include "wkb_split.h" #include "wkb.h" PGDLLEXPORT PG_FUNCTION_INFO_V1(h3_cells_to_multi_polygon_wkb); /* Converts LinkedGeoPolygon vertex coordinates to degrees in place */ static void linked_geo_polygon_to_degs(LinkedGeoPolygon * multiPolygon); Datum h3_cells_to_multi_polygon_wkb(PG_FUNCTION_ARGS) { ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); LinkedGeoPolygon *linkedPolygon; H3Error error; int numHexes; ArrayIterator iterator; Datum value; bool isnull; H3Index *h3set; bytea *wkb; numHexes = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); h3set = palloc(numHexes * sizeof(H3Index)); /* Extract data from array into h3set */ iterator = array_create_iterator(array, 0, NULL); numHexes = 0; while (array_iterate(iterator, &value, &isnull)) { h3set[numHexes++] = DatumGetH3Index(value); } /* produce hexagons into allocated memory */ linkedPolygon = palloc(sizeof(LinkedGeoPolygon)); h3_assert(cellsToLinkedMultiPolygon(h3set, numHexes, linkedPolygon)); if (is_linked_polygon_crossed_by_180(linkedPolygon)) { /* Split by 180th meridian */ LinkedGeoPolygon *splitPolygon = split_linked_polygon_by_180(linkedPolygon); linked_geo_polygon_to_degs(splitPolygon); wkb = linked_geo_polygon_to_wkb(splitPolygon); free_linked_geo_polygon(splitPolygon); } else { linked_geo_polygon_to_degs(linkedPolygon); wkb = linked_geo_polygon_to_wkb(linkedPolygon); } destroyLinkedMultiPolygon(linkedPolygon); pfree(linkedPolygon); PG_RETURN_BYTEA_P(wkb); } void linked_geo_polygon_to_degs(LinkedGeoPolygon * multiPolygon) { FOREACH_LINKED_POLYGON_NOCONST(multiPolygon, polygon) { FOREACH_LINKED_LOOP_NOCONST(polygon, loop) { FOREACH_LINKED_LAT_LNG_NOCONST(loop, latlng) { LatLng *vertex = &latlng->vertex; vertex->lat = radsToDegs(vertex->lat); vertex->lng = radsToDegs(vertex->lng); } } } } h3-pg-4.2.2/h3_postgis/src/wkb_split.c000066400000000000000000000546461475234715600175050ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include #include #include "constants.h" #include "wkb_split.h" #include "wkb_bbox3.h" #include "wkb_linked_geo.h" /* Example: O-shaped polygon crossed by antimeridian. After intersections are found and sorted by latitude, intersection pairs 0-1 and 2-3 become segments in exterior rings of the polygons in result. | +------(0)------+ | | | | +---(1)---+ | | | | | | | | | | | | +---(2)---+ | | | | +------(3)------+ | Algorithm overview: 1. Initialization: - empty array of vertices - empty array of intersections - empty array of interior rings not split by antimeridian 2. Processing polygon rings: for each ring in the polygon: if ring is crossed by prime or antimeridian: for each segment in the ring: - add first endpoint to array of vertices if segment crosses antimeridian: - add an intersection, link to first endpoint else: - add ring to array of non-split holes 3. Preparing data: - sort intersections by latitude - set sort order value for each intersection 4. Building multipolygon: while there are unused vertices: - create empty exterior ring - start traversing vertex array forward starting from next unused vertex while current vertex is unused: - add current vertex to exterior ring - get next vertex (depends on traversal direction) if there is an intersection between vertices: - add intersection point to exterior ring - get adjacent intersection from sorted array - add next intersection point to exterior ring - update traversal direction based on intersection direction value - get next vertex - move to text vertex - check which non-split holes are inside the exterior ring and add them to polygon - add polygon to result */ #define SIGN(x) ((x < 0) ? -1 : (x > 0) ? 1 \ : 0) #define FP_EQUAL(v1, v2) ((v1) == (v2) || fabs((v1) - (v2)) < DBL_EPSILON) #define SPLIT_ASSERT(condition, message) \ ASSERT( \ condition, \ ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, \ message) #define SPLIT_ASSERT_VALID_VERTEX_IDX(split, idx) \ SPLIT_ASSERT(0 <= idx && idx < split->vertexNum, "vertex index out of bounds") #define INTERSECT_ARRAY_SIZE_INIT (4) /* Direction of segment intersecting antimeridian: */ /* West-to-East or East-to-West */ typedef enum { SplitIntersectDir_None = 0, SplitIntersectDir_WE, SplitIntersectDir_EW } SplitIntersectDir; typedef struct { SplitIntersectDir dir; bool isPrime; double lat; int vertexIdx; int sortOrder; } SplitIntersect; typedef struct { const LatLng *latlngPtr; int intersectIdx; short sign; /* longitude sign is set explicitly in case * longitude of the vertex itself is zero */ int link; /* links first and last vertices in a ring */ } SplitVertex; typedef struct { /* Vertices */ int vertexNum; SplitVertex *vertices; /* Intersections */ int maxIntersectNum; int intersectNum; SplitIntersect *intersects; SplitIntersect **sortedIntersects; /* Non-split holes */ int holeNum; const LinkedGeoLoop **holes; } Split; static bool is_polygon_crossed(const LinkedGeoPolygon * polygon); static LinkedGeoPolygon * split_polygon(const LinkedGeoPolygon * polygon); static bool is_ring_crossed(const LinkedGeoLoop * ring); static void split_init(Split * split, int ringNum, int vertexNum); static void split_cleanup(Split * split); static void split_process_ring(Split * split, const LinkedGeoLoop * ring); static void split_prepare(Split * split); static LinkedGeoPolygon * split_create_multi_polygon(Split * split); static int split_add_vertex(Split * split, const LatLng * latlng); static void split_add_intersect_after(Split * split, int vertexIdx, SplitIntersectDir dir, bool isPrime, double lat); static int split_add_intersect(Split * split, SplitIntersectDir dir, bool isPrime, double lat); static void split_link_vertices(Split * split, int idx1, int idx2); static void split_add_hole(Split * split, const LinkedGeoLoop * hole); static void split_sort_intersects(Split * split); static int split_intersect_ptr_cmp(const void *a, const void *b); static int split_find_next_vertex(Split * split, int *start); static LinkedGeoPolygon * split_create_polygon_vertex(Split * split, int vertexIdx); static void split_polygon_assign_holes(Split * split, short sign, LinkedGeoPolygon * polygon); static const SplitIntersect * split_get_intersect_after(Split * split, int vertexIdx); static void split_intersect_get_lat_lng(const SplitIntersect * intersect, short sign, LatLng * latlng); static int count_polygon_vertices(const LinkedGeoPolygon * polygon, int *ringNum); static short lat_lng_ring_pos(const LinkedGeoLoop * ring, short sign, const Bbox3 * bbox, const LatLng * latlng); static short segment_intersect(const Vect3 * v1, const Vect3 * v2, const Vect3 * u1, const Vect3 * u2); static short point_segment_pos(const Vect3 * v1, const Vect3 * v2, const Vect3 * p); static void add_lat_lng(LinkedGeoLoop * loop, const LatLng * latlng); bool is_linked_polygon_crossed_by_180(const LinkedGeoPolygon * multiPolygon) { FOREACH_LINKED_POLYGON(multiPolygon, polygon) { if (is_polygon_crossed(polygon)) return true; } return false; } LinkedGeoPolygon * split_linked_polygon_by_180(const LinkedGeoPolygon * multiPolygon) { LinkedGeoPolygon *result = NULL; LinkedGeoPolygon *last = NULL; FOREACH_LINKED_POLYGON(multiPolygon, polygon) { /* Split or copy next polygon */ LinkedGeoPolygon *nextResult = is_polygon_crossed(polygon) ? split_polygon(polygon) : copy_linked_geo_polygon(polygon); /* Add to result */ if (!result) { result = nextResult; last = nextResult; } else { last->next = nextResult; } while (last->next) last = last->next; } return result; } double split_180_lat(const LatLng * coord1, const LatLng * coord2) { Vect3 p1, p2, normal, s; double y; /* Normal of circle containing points: normal = p1 x p2 */ vect3_from_lat_lng(coord1, &p1); vect3_from_lat_lng(coord2, &p2); vect3_cross(&p1, &p2, &normal); /* y coordinate of 0/180 meridian circle normal */ y = (coord1->lng < 0 || coord2->lng > 0) ? -1 : 1; /* Circle plane intersection vector: s = (p1 x p2) x {0, y, 0} */ s.x = -(normal.z * y); s.y = 0; s.z = normal.x * y; vect3_normalize(&s); /* intersection point coordinates on unit * sphere */ return asin(s.z); /* latitude */ } bool is_polygon_crossed(const LinkedGeoPolygon * polygon) { return polygon->first ? is_ring_crossed(polygon->first) : false; } LinkedGeoPolygon * split_polygon(const LinkedGeoPolygon * polygon) { int ringNum, vertexNum; Split split; LinkedGeoPolygon *result; /* Init data */ vertexNum = count_polygon_vertices(polygon, &ringNum); split_init(&split, ringNum, vertexNum); /* Process rings */ FOREACH_LINKED_LOOP(polygon, ring) { if (ring == polygon->first || is_ring_crossed(ring)) split_process_ring(&split, ring); else split_add_hole(&split, ring); } /* Prepare data */ split_prepare(&split); /* Build result */ result = split_create_multi_polygon(&split); /* Cleanup */ split_cleanup(&split); return result; } bool is_ring_crossed(const LinkedGeoLoop * ring) { if (!ring->first || !ring->first->next) return false; FOREACH_LINKED_LAT_LNG_PAIR(ring, cur, next) { double lng = cur->vertex.lng; double nextLng = next->vertex.lng; if (SIGN(lng) != SIGN(nextLng) && fabs(lng - nextLng) > M_PI) { return true; } } return false; } void split_init(Split * split, int ringNum, int vertexNum) { *split = (Split) { 0 }; split->vertices = palloc0(vertexNum * sizeof(SplitVertex)); split->maxIntersectNum = INTERSECT_ARRAY_SIZE_INIT; split->intersects = palloc0(split->maxIntersectNum * sizeof(SplitIntersect)); if (ringNum > 1) split->holes = palloc0((ringNum - 1) * sizeof(LinkedGeoLoop *)); } void split_cleanup(Split * split) { if (split->vertices) pfree(split->vertices); if (split->intersects) pfree(split->intersects); if (split->sortedIntersects) pfree(split->sortedIntersects); if (split->holes) pfree(split->holes); *split = (Split) { 0 }; } void split_process_ring(Split * split, const LinkedGeoLoop * ring) { short sign = 0; int vertexIdx = -1; int firstVertexIdx = -1; SPLIT_ASSERT(ring->first && ring->first->next, "polygon ring must have at least 2 vertices"); FOREACH_LINKED_LAT_LNG_PAIR(ring, cur, next) { double lng, nextLng; short nextSign; /* Add vertex */ vertexIdx = split_add_vertex(split, &cur->vertex); if (firstVertexIdx < 0) firstVertexIdx = vertexIdx; lng = cur->vertex.lng; nextLng = next->vertex.lng; nextSign = SIGN(nextLng); if (sign == 0) { sign = SIGN(lng); if (sign != 0) { /* Set sign for vertices traversed so far */ for (int i = firstVertexIdx; i <= vertexIdx; ++i) split->vertices[i].sign = sign; } } else { /* Set vertex sign */ split->vertices[vertexIdx].sign = sign; } if (sign != 0 && nextSign != 0 && nextSign != sign) { /* Prime or antimeridian crossed */ /* Add intersection after current vertex */ SplitIntersectDir dir = (sign < 0) ? SplitIntersectDir_WE : SplitIntersectDir_EW; int isPrime = (fabs(lng - nextLng) < M_PI); double lat = split_180_lat(&cur->vertex, &next->vertex); split_add_intersect_after(split, vertexIdx, dir, isPrime, lat); sign = nextSign; } } /* Link first and last vertices */ split_link_vertices(split, firstVertexIdx, vertexIdx); } void split_prepare(Split * split) { split_sort_intersects(split); } LinkedGeoPolygon * split_create_multi_polygon(Split * split) { LinkedGeoPolygon *multiPolygon = NULL; LinkedGeoPolygon *lastPolygon = NULL; int vertexIdxStart = 0; while (true) { LinkedGeoPolygon *polygon; /* Get next unused vertex */ int vertexIdx = split_find_next_vertex(split, &vertexIdxStart); if (vertexIdx < 0) break; /* done */ /* Create next polygon */ polygon = split_create_polygon_vertex(split, vertexIdx); /* Add to result */ if (!multiPolygon) multiPolygon = polygon; else lastPolygon->next = polygon; lastPolygon = polygon; } return multiPolygon; } int split_add_vertex(Split * split, const LatLng * latlng) { int idx = split->vertexNum++; SplitVertex *vertex = &split->vertices[idx]; vertex->latlngPtr = latlng; vertex->intersectIdx = -1; vertex->sign = 0; vertex->link = -1; return idx; } void split_add_intersect_after(Split * split, int vertexIdx, SplitIntersectDir dir, bool isPrime, double lat) { int idx = split_add_intersect(split, dir, isPrime, lat); SplitIntersect *intersect = &split->intersects[idx]; intersect->vertexIdx = vertexIdx; split->vertices[vertexIdx].intersectIdx = idx; } int split_add_intersect(Split * split, SplitIntersectDir dir, bool isPrime, double lat) { int idx; SplitIntersect *intersect; if (split->intersectNum == split->maxIntersectNum) { /* Reallocate memory for intersections */ int maxNum = split->maxIntersectNum * 2; if (maxNum > split->vertexNum) maxNum = split->vertexNum; split->intersects = repalloc(split->intersects, maxNum * sizeof(SplitIntersect)); split->maxIntersectNum = maxNum; } idx = split->intersectNum++; intersect = &split->intersects[idx]; intersect->dir = dir; intersect->isPrime = isPrime; intersect->lat = lat; intersect->vertexIdx = -1; intersect->sortOrder = -1; return idx; } void split_link_vertices(Split * split, int idx1, int idx2) { SPLIT_ASSERT_VALID_VERTEX_IDX(split, idx1); SPLIT_ASSERT_VALID_VERTEX_IDX(split, idx2); split->vertices[idx1].link = idx2; split->vertices[idx2].link = idx1; } void split_add_hole(Split * split, const LinkedGeoLoop * hole) { split->holes[split->holeNum++] = hole; } void split_sort_intersects(Split * split) { SPLIT_ASSERT(split->intersectNum % 2 == 0, "intersection number must be even"); /* Collect intesection pointers */ split->sortedIntersects = palloc(split->intersectNum * sizeof(SplitIntersect *)); for (int i = 0; i < split->intersectNum; ++i) { SplitIntersect *intersect = &split->intersects[i]; split->sortedIntersects[i] = intersect; } /* Sort intersection pointers */ qsort( split->sortedIntersects, split->intersectNum, sizeof(SplitIntersect *), &split_intersect_ptr_cmp); /* Assign sort order values to intersections */ for (int i = 0; i < split->intersectNum; ++i) split->sortedIntersects[i]->sortOrder = i; } /** Sort intersections by latitude Sort value for points on prime meridian: * 180 - lat, if lat >= 0 * -180 - lat, if lat < 0 -90 0 90 ------*----------+----------*------> meridian: prime antimeridian prime value: -180-lat lat 180-lat */ int split_intersect_ptr_cmp(const void *a, const void *b) { const SplitIntersect *i1 = *((const SplitIntersect **) a); const SplitIntersect *i2 = *((const SplitIntersect **) b); double v1, v2; v1 = i1->lat; if (i1->isPrime) v1 = ((v1 < 0) ? -M_PI : M_PI) - v1; v2 = i2->lat; if (i2->isPrime) v2 = ((v2 < 0) ? -M_PI : M_PI) - v2; return (v1 == v2) ? 0 : (v1 < v2) ? -1 : 1; } int split_find_next_vertex(Split * split, int *start) { for (int i = *start; i < split->vertexNum; ++i) { if (split->vertices[i].latlngPtr) { *start = i + 1; return i; } } return -1; } LinkedGeoPolygon * split_create_polygon_vertex(Split * split, int vertexIdx) { LinkedGeoPolygon *polygon; LinkedGeoLoop *loop; int idx, nextIdx, intersectIdx; SplitVertex *vertex; const SplitIntersect *intersect; short sign, step; polygon = palloc0(sizeof(LinkedGeoPolygon)); loop = palloc0(sizeof(LinkedGeoLoop)); add_linked_geo_loop(polygon, loop); idx = vertexIdx; vertex = &split->vertices[idx]; sign = vertex->sign; step = 1; /* vertex array traversal direction */ while (vertex->latlngPtr) { /* Add vertex */ add_lat_lng(loop, vertex->latlngPtr); vertex->latlngPtr = NULL; /* * Get indices of the other segment endpoint and potential * intersection */ if (vertex->link > -1 && ((step > 0) == (idx > vertex->link))) { nextIdx = vertex->link; /* Potential intersection is after last ring vertex */ intersectIdx = (nextIdx > idx) ? nextIdx : idx; } else { nextIdx = idx + step; /* Potential intersection is after first vertex */ intersectIdx = (nextIdx > idx) ? idx : nextIdx; } /* Is there an intersection after the vertex? */ SPLIT_ASSERT_VALID_VERTEX_IDX(split, intersectIdx); intersect = split_get_intersect_after(split, intersectIdx); if (intersect) { LatLng latlng; int intersectSortOrder; /* Add intersection vertex */ split_intersect_get_lat_lng(intersect, sign, &latlng); add_lat_lng(loop, &latlng); /* Find next intersection */ intersectSortOrder = (intersect->sortOrder % 2 == 0) ? intersect->sortOrder + 1 : intersect->sortOrder - 1; intersect = split->sortedIntersects[intersectSortOrder]; intersectIdx = intersect->vertexIdx; /* Add next intersection vertex */ split_intersect_get_lat_lng(intersect, sign, &latlng); add_lat_lng(loop, &latlng); /* * Does intersecting segment end in the same hemisphere where the * polygon is located? */ step = ((sign > 0) == (intersect->dir == SplitIntersectDir_WE)) ? 1 : -1; if (step > 0) { /* Next vertex is the second endpoint of a segment */ const SplitVertex *segmentStart = &split->vertices[intersectIdx]; if (segmentStart->link > -1 && intersectIdx > segmentStart->link) nextIdx = segmentStart->link; else nextIdx = intersectIdx + 1; } else { /* Next vertex is the first endpoint of a segment */ nextIdx = intersectIdx; } } idx = nextIdx; vertex = &split->vertices[idx]; } /* Assign holes */ split_polygon_assign_holes(split, sign, polygon); return polygon; } void split_polygon_assign_holes(Split * split, short sign, LinkedGeoPolygon * polygon) { const LinkedGeoLoop *outerLoop; Bbox3 bbox; outerLoop = polygon->first; bbox3_from_linked_loop(outerLoop, &bbox); for (int i = 0; i < split->holeNum; ++i) { const LinkedGeoLoop *hole; short pos = 0; hole = split->holes[i]; if (!hole) continue; /* Check if hole vertices are inside the outher shell of the polygon */ FOREACH_LINKED_LAT_LNG(hole, cur) { pos = lat_lng_ring_pos(outerLoop, sign, &bbox, &cur->vertex); if (pos != 0) break; /* vertex is either inside or outside */ } if (pos != -1) { /* Add hole loop copy to polygon */ LinkedGeoLoop *hole_copy = copy_linked_geo_loop(hole); add_linked_geo_loop(polygon, hole_copy); /* Remove hole from the list */ split->holes[i] = NULL; } } } const SplitIntersect * split_get_intersect_after(Split * split, int vertexIdx) { int idx; SPLIT_ASSERT_VALID_VERTEX_IDX(split, vertexIdx); idx = split->vertices[vertexIdx].intersectIdx; return (idx > -1) ? &split->intersects[idx] : NULL; } void split_intersect_get_lat_lng(const SplitIntersect * intersect, short sign, LatLng * latlng) { latlng->lat = intersect->lat; if (intersect->isPrime) latlng->lng = 0.0; else latlng->lng = (sign > 0) ? M_PI : -M_PI; } int count_polygon_vertices(const LinkedGeoPolygon * polygon, int *ringNum) { int num = 0; if (ringNum) *ringNum = 0; FOREACH_LINKED_LOOP(polygon, ring) { if (ringNum) ++(*ringNum); num += count_linked_lat_lng(ring); } return num; } short lat_lng_ring_pos(const LinkedGeoLoop * ring, short sign, const Bbox3 * bbox, const LatLng * latlng) { short signLatlng; Vect3 vect, outVect; LatLng out; int intersectNum = 0; Vect3 curVect, nextVect; /* Check longitude sign */ signLatlng = SIGN(latlng->lng); if (signLatlng != 0 && signLatlng != sign) return -1; vect3_from_lat_lng(latlng, &vect); /* Check bbox */ if (!bbox3_contains_vect3(bbox, &vect)) return -1; /* Create a point that's guaranteed to be outside the polygon */ out.lng = (latlng->lng == 0) ? -sign * 1e-10 : -latlng->lng; out.lat = latlng->lat; vect3_from_lat_lng(&out, &outVect); /* Check if ring is a single vertex exactly matching the point */ if (!ring->first->next) return true; /* * Count a number of intersections between the ring and (latlng, out) * segment */ intersectNum = 0; vect3_from_lat_lng(&ring->first->vertex, &curVect); FOREACH_LINKED_LAT_LNG_PAIR(ring, cur, next) { short intersect; /* Check if point matches ring vertex */ if (vect3_eq(&vect, &curVect)) return 0; /* Next vertex */ vect3_from_lat_lng(&next->vertex, &nextVect); if (!vect3_eq(&curVect, &nextVect)) { intersect = segment_intersect(&curVect, &nextVect, &vect, &outVect); if (intersect == 0) return 0; /* point on ring segment */ if (intersect > 0) ++intersectNum; } curVect = nextVect; } return (intersectNum % 2 == 0) ? -1 : 1; } short segment_intersect(const Vect3 * v1, const Vect3 * v2, const Vect3 * u1, const Vect3 * u2) { Vect3 vn, un; short v1Side, v2Side, u1Side, u2Side; double normalDot; /* Normals of V and U planes */ vect3_cross(v1, v2, &vn); vect3_normalize(&vn); vect3_cross(u1, u2, &un); vect3_normalize(&un); /* Are the planes the same? */ normalDot = vect3_dot(&vn, &un); if (FP_EQUAL(fabs(normalDot), 1.0)) { short ret = point_segment_pos(v1, v2, u1); if (ret == -1) ret = point_segment_pos(v1, v2, u2); if (ret == -1) ret = point_segment_pos(u1, u2, v1); if (ret == -1) ret = point_segment_pos(u1, u2, v2); return ret; } /* Which side of other segment are segment endpoints? */ v1Side = SIGN(vect3_dot(&un, v1)); v2Side = SIGN(vect3_dot(&un, v2)); u1Side = SIGN(vect3_dot(&vn, u1)); u2Side = SIGN(vect3_dot(&vn, u2)); /* v1 and v2 are on the same side of U plane */ if (v1Side == v2Side && v1Side != 0) return -1; /* u1 and u2 are on the same side of V plane */ if (u1Side == u2Side && u2Side != 0) return -1; if (v1Side != v2Side && (v1Side + v2Side) == 0 && u1Side != u2Side && (u1Side + u2Side) == 0) { /* Intersection point */ Vect3 intersect; vect3_cross(&vn, &un, &intersect); vect3_normalize(&intersect); /* Is intersection point inside both arcs? */ if (point_segment_pos(v1, v2, &intersect) != -1 && point_segment_pos(u1, u2, &intersect) != -1) { return 1; } /* Try antipodal intersection point */ vect3_scale(&intersect, -1.0); if (point_segment_pos(v1, v2, &intersect) != -1 && point_segment_pos(u1, u2, &intersect) != -1) { return 1; } return -1; } return 0; } short point_segment_pos(const Vect3 * v1, const Vect3 * v2, const Vect3 * p) { Vect3 middle; double minSimilarity; /* Check if point is same as one of segment endpoints */ if (vect3_eq(p, v1) || vect3_eq(p, v2)) return 0; /* Find vector bisecting anble between segment endpoint vectors */ vect3_sum(v1, v2, &middle); vect3_normalize(&middle); /* How similar are endpoint vectors to bisecting vector? */ minSimilarity = vect3_dot(v1, &middle); if (fabs(1.0 - minSimilarity) > 1e-10) { /* Segment is long enough to use dot product test. */ /* If point vector is more similar to bisecting vector, */ /* * then it must be closer to center, so the point is inside the * segment. */ return (vect3_dot(p, &middle) > minSimilarity) ? 1 : -1; } else { /* Segment is too short to use dot product test. */ /* * Check if vectors from segment endpoints to the point are in * opposite directions. */ Vect3 d1, d2; vect3_diff(p, v1, &d1); vect3_normalize(&d1); vect3_diff(p, v2, &d2); vect3_normalize(&d2); return (vect3_dot(&d1, &d2) < 0.0) ? 1 : -1; } } void add_lat_lng(LinkedGeoLoop * loop, const LatLng * latlng) { LinkedLatLng *linked; if (loop->last) { /* Does new vertex exactly match the last one? */ const LatLng *last = &loop->last->vertex; if (last->lat == latlng->lat && last->lng == latlng->lng) return; } linked = palloc0(sizeof(LinkedLatLng)); linked->vertex.lat = latlng->lat; linked->vertex.lng = latlng->lng; add_linked_lat_lng(loop, linked); } h3-pg-4.2.2/h3_postgis/src/wkb_split.h000066400000000000000000000017061475234715600174770ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #ifndef PGH3_WKB_SPLIT_H #define PGH3_WKB_SPLIT_H #include #include #include "error.h" bool is_linked_polygon_crossed_by_180(const LinkedGeoPolygon * multiPolygon); LinkedGeoPolygon * split_linked_polygon_by_180(const LinkedGeoPolygon * multiPolygon); double split_180_lat(const LatLng * coord1, const LatLng * coord2); #endif h3-pg-4.2.2/h3_postgis/src/wkb_vect3.c000066400000000000000000000044361475234715600173660ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #include #include #include "wkb_vect3.h" #define FP_EQUAL(v1, v2) ((v1) == (v2) || fabs((v1) - (v2)) < DBL_EPSILON) void vect3_from_lat_lng(const LatLng * coord, Vect3 * vect) { vect->x = cos(coord->lat) * cos(coord->lng); vect->y = cos(coord->lat) * sin(coord->lng); vect->z = sin(coord->lat); } void vect3_to_lat_lng(const Vect3 * vect, LatLng * coord) { coord->lng = atan2(vect->y, vect->x); coord->lat = asin(vect->z); } int vect3_eq(const Vect3 * vect1, const Vect3 * vect2) { return FP_EQUAL(vect1->x, vect2->x) && FP_EQUAL(vect1->y, vect2->y) && FP_EQUAL(vect1->z, vect2->z); } void vect3_normalize(Vect3 * vect) { double len = sqrt(vect->x * vect->x + vect->y * vect->y + vect->z * vect->z); if (len > 0) { vect->x = vect->x / len; vect->y = vect->y / len; vect->z = vect->z / len; } else { vect->x = 0; vect->y = 0; vect->z = 0; } } void vect3_sum(const Vect3 * vect1, const Vect3 * vect2, Vect3 * sum) { sum->x = vect1->x + vect2->x; sum->y = vect1->y + vect2->y; sum->z = vect1->z + vect2->z; } void vect3_diff(const Vect3 * vect1, const Vect3 * vect2, Vect3 * diff) { diff->x = vect1->x - vect2->x; diff->y = vect1->y - vect2->y; diff->z = vect1->z - vect2->z; } void vect3_scale(Vect3 * vect, double factor) { vect->x *= factor; vect->y *= factor; vect->z *= factor; } void vect3_cross(const Vect3 * vect1, const Vect3 * vect2, Vect3 * prod) { prod->x = vect1->y * vect2->z - vect1->z * vect2->y; prod->y = vect1->z * vect2->x - vect1->x * vect2->z; prod->z = vect1->x * vect2->y - vect1->y * vect2->x; } double vect3_dot(const Vect3 * vect1, const Vect3 * vect2) { return (vect1->x * vect2->x) + (vect1->y * vect2->y) + (vect1->z * vect2->z); } h3-pg-4.2.2/h3_postgis/src/wkb_vect3.h000066400000000000000000000025041475234715600173650ustar00rootroot00000000000000/* * Copyright 2024 Zacharias Knudsen * * 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. */ #ifndef PGH3_VECT3_H #define PGH3_VECT3_H #include #include #include typedef struct { double x; double y; double z; } Vect3; void vect3_from_lat_lng(const LatLng * coord, Vect3 * vect); void vect3_to_lat_lng(const Vect3 * vect, LatLng * coord); int vect3_eq(const Vect3 * vect1, const Vect3 * vect2); void vect3_normalize(Vect3 * vect); void vect3_sum(const Vect3 * vect1, const Vect3 * vect2, Vect3 * sum); void vect3_diff(const Vect3 * vect1, const Vect3 * vect2, Vect3 * diff); void vect3_scale(Vect3 * vect, double factor); void vect3_cross(const Vect3 * vect1, const Vect3 * vect2, Vect3 * prod); double vect3_dot(const Vect3 * vect1, const Vect3 * vect2); #endif h3-pg-4.2.2/h3_postgis/test/000077500000000000000000000000001475234715600155145ustar00rootroot00000000000000h3-pg-4.2.2/h3_postgis/test/CMakeLists.txt000066400000000000000000000012511475234715600202530ustar00rootroot00000000000000set(TESTS postgis rasters ) if(PostgreSQL_REGRESS) add_test( NAME "h3_postgis_regress" COMMAND ${PostgreSQL_REGRESS} --temp-instance=${CMAKE_BINARY_DIR}/tmp --bindir=${PostgreSQL_BIN_DIR} --inputdir=${CMAKE_CURRENT_SOURCE_DIR} --outputdir=${CMAKE_CURRENT_BINARY_DIR} --load-extension h3 --load-extension postgis --load-extension postgis_raster --load-extension h3_postgis ${TESTS} ) endif() if(PostgreSQL_VALIDATE_EXTUPGRADE) add_test( NAME "h3_postgis_validate_extupgrade" COMMAND pg_validate_extupgrade --extname h3_postgis --from 4.0.0 --to ${INSTALL_VERSION} ) endif() h3-pg-4.2.2/h3_postgis/test/expected/000077500000000000000000000000001475234715600173155ustar00rootroot00000000000000h3-pg-4.2.2/h3_postgis/test/expected/postgis.out000066400000000000000000000210101475234715600215300ustar00rootroot00000000000000\pset tuples_only on -- Variables for testing \set resolution 10 \set hexagon '\'8a63a9a99047fff\'' \set meter ST_SetSRID(ST_Point(6196902.235389061,1413172.0833316022), 3857) \set degree ST_SetSRID(ST_Point(55.6677199224442,12.592131261648213), 4326) \set edgecross '\'8003fffffffffff\'::h3index' \set polar '\'81f2bffffffffff\'::h3index' \set lat1 84.76455330449812 \set lat2 89.980298101841 \set epsilon 0.0000000000001 \set longPathEndpoint1 '\'842ca2bffffffff\'::h3index' \set longPathEndpoint2 '\'842e611ffffffff\'::h3index' -- polygon with 2 holes \set with2holes '\'POLYGON((31.6520834 68.9912098,31.6521263 68.9910944,31.6530919 68.9912021,31.6540789 68.9912944,31.6550016 68.991279,31.6553449 68.9910175,31.6554737 68.9907713,31.6559458 68.990402,31.6563535 68.9900789,31.6568684 68.9897404,31.6569543 68.9895711,31.6567182 68.988671,31.6569114 68.9881786,31.6571045 68.9879401,31.6567611 68.9875708,31.6570401 68.9873015,31.6576195 68.986986,31.6581774 68.9868398,31.6584992 68.9870553,31.6586065 68.9872707,31.6590786 68.9873246,31.6594219 68.9872091,31.6596579 68.9870091,31.6596579 68.9867706,31.6601515 68.9866167,31.6607308 68.9864551,31.660409 68.986232,31.6601729 68.9860627,31.6605806 68.9859319,31.6614818 68.9859011,31.6620183 68.9857087,31.6622972 68.9854471,31.6628337 68.9852932,31.6633701 68.9852855,31.663928 68.9855702,31.6640782 68.9859088,31.6636705 68.9862166,31.6639924 68.9864859,31.664443 68.9868629,31.664679 68.9872091,31.6642928 68.9873784,31.6641426 68.9876939,31.6650009 68.9879016,31.6652155 68.9881555,31.6653657 68.9883709,31.6659665 68.9886941,31.6662884 68.9889941,31.666739 68.989248,31.6669321 68.9891095,31.6670394 68.9888787,31.6681123 68.9889326,31.6687345 68.989325,31.6692495 68.9895865,31.6701937 68.9897635,31.6710949 68.9897404,31.6725325 68.9897558,31.6733479 68.9898558,31.6743135 68.9904097,31.674571 68.990702,31.6747641 68.9909328,31.6745066 68.9911405,31.6738844 68.9912636,31.6731977 68.9914175,31.6734981 68.9916098,31.6739487 68.9915713,31.6744852 68.9915175,31.6750431 68.9914021,31.6751718 68.9911944,31.6752147 68.9910405,31.6756439 68.9910636,31.6765451 68.9912021,31.6777253 68.9912944,31.6784119 68.9912482,31.6790771 68.9911559,31.6793346 68.9913021,31.6787553 68.9916713,31.678133 68.9920867,31.6780472 68.9924483,31.6782617 68.9927098,31.6792702 68.9941098,31.6794419 68.9943636,31.6801715 68.9945328,31.6817808 68.9946328,31.6825533 68.9948174,31.6827249 68.995202,31.683712 68.9957404,31.6840124 68.9962634,31.684699 68.9965556,31.6848492 68.9968171,31.6841197 68.9969632,31.6831326 68.9969479,31.6827464 68.9969171,31.6824031 68.9968556,31.6821456 68.9967248,31.6813302 68.9968248,31.6810083 68.9971094,31.6806865 68.9971863,31.6802358 68.9971401,31.6792702 68.9967018,31.6787553 68.9963864,31.6780901 68.9958327,31.6777038 68.9956788,31.6766095 68.9955635,31.6762233 68.9954943,31.6759658 68.9952943,31.6753649 68.9951481,31.6746354 68.9952712,31.6735625 68.9951866,31.6728329 68.9951866,31.6726398 68.9953943,31.6719746 68.9955173,31.6709661 68.9954173,31.6704941 68.9951558,31.6700434 68.9948251,31.669743 68.9944867,31.6695285 68.994179,31.6693783 68.9939329,31.6690779 68.993756,31.6680479 68.9935867,31.663692 68.9929329,31.6628551 68.9927637,31.661675 68.9927637,31.6610527 68.9929637,31.6605377 68.9929714,31.6599583 68.9929406,31.6588855 68.9928175,31.658349 68.992656,31.657598 68.9923714,31.6567826 68.9922329,31.6552162 68.9920714,31.6541648 68.9919175,31.6531348 68.9916483,31.6523838 68.9914175,31.6520834 68.9912098),(31.657185 68.990202,31.657244 68.9903482,31.6574585 68.9903809,31.6578555 68.9903597,31.6581452 68.9902539,31.6583651 68.9900924,31.6582364 68.9899366,31.6578716 68.9898616,31.6575766 68.9898731,31.6573083 68.9899808,31.6572332 68.9900731,31.657185 68.990202),(31.6590196 68.9886094,31.6591644 68.9887229,31.6594058 68.9888537,31.6595936 68.9889557,31.6596794 68.9890441,31.6597116 68.9891345,31.659776 68.9892519,31.6599476 68.9891903,31.660012 68.9890614,31.6601139 68.9889383,31.6602641 68.9888326,31.6602749 68.988721,31.6601998 68.988596,31.6600603 68.988369,31.6601676 68.9882709,31.6604251 68.9882844,31.6607255 68.9882728,31.6609454 68.9881767,31.6605592 68.9880901,31.6604841 68.9879689,31.6603607 68.9878497,31.660071 68.9878843,31.6597867 68.9879959,31.6593575 68.9880305,31.6592127 68.9881132,31.6592234 68.9882421,31.6592127 68.9883786,31.6590196 68.9886094))\''::geometry(POLYGON) -- S-shaped polygon with 4 holes crossing antimeridian \set transmeridianWithHoles '\'POLYGON((-170 12.5, 170 12.5, 170 7.5, -175 7.5, -175 2.5, 170 2.5, 170 -12.5, -170 -12.5, -170 -7.5, 175 -7.5, 175 -2.5, -170 -2.5, -170 12.5), (-176 11.5, -179 11.5, -179 8.5, -176 8.5, -176 11.5), (174 11.5, 171 11.5, 171 8.5, 174 8.5, 174 11.5), (174 -3.5, 171 -3.5, 171 -6.5, 174 -6.5, 174 -3.5),(-176 -8.5, -179 -8.5, -179 -11.5, -176 -11.5, -176 -8.5))\''::geometry(POLYGON) -- multipolygon with 2 polygons: one crossing and one not crossing antimeridian \set transmeridianMulti '\'MULTIPOLYGON(((-175 50, -175 55, 175 55, 175 50, -175 50)), ((170 50, 170 55, 165 55, 165 50, 170 50)))\''::geometry(MULTIPOLYGON) SELECT h3_lat_lng_to_cell(:degree, :resolution) = '8a63a9a99047fff'; t -- meters are NOT reprojected SELECT h3_lat_lng_to_cell(:meter, :resolution) <> '8a63a9a99047fff'; t -- check back/forth conversion return same hex SELECT h3_lat_lng_to_cell(h3_cell_to_geometry(:hexagon), :resolution) = '8a63a9a99047fff'; t -- check num points in boundary SELECT ST_NPoints(h3_cell_to_boundary_geometry(:hexagon)) = 7; t -- test strict h3_lat_lng_to_cell throws for bad latlon CREATE FUNCTION h3_test_postgis_nounit() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_lat_lng_to_cell(POINT(360, 2.592131261648213), 1); RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; SET h3.strict TO true; SELECT h3_test_postgis_nounit(); t SET h3.strict TO false; DROP FUNCTION h3_test_postgis_nounit; -- Test wraparound \set lon 55.6677199224442 \set lat 12.592131261648213 SELECT h3_lat_lng_to_cell(POINT(:lon, :lat), 7) = h3_lat_lng_to_cell(POINT(:lon + 360, :lat), 7); t SELECT h3_lat_lng_to_cell(POINT(:lon, :lat ), 7) = h3_lat_lng_to_cell(POINT(:lon, :lat + 360), 7); t -- test h3_grid_path_cells_recursive works for long path SELECT COUNT(*) > 0 FROM ( SELECT h3_grid_path_cells_recursive(:longPathEndpoint1, :longPathEndpoint1) ) q; t -- h3_polygon_to_cells works for polygon with two holes SELECT COUNT(*) = 48 FROM ( SELECT h3_polygon_to_cells(:with2holes, 10) ) q; t -- -- Test h3_cell_to_boundary_wkb -- -- polyfill of geo boundary returns original index SELECT h3_polygon_to_cells(h3_cell_to_boundary(:hexagon)::geometry::polygon, null, :resolution) = :hexagon; t -- the boundary of a non-edgecrossing index is a polygon SELECT GeometryType(h3_cell_to_boundary_wkb(:hexagon)::geometry) LIKE 'POLYGON'; t -- the boundary of an edgecrossing index is a multipolygon when split SELECT GeometryType(h3_cell_to_boundary_wkb(:edgecross)::geometry) LIKE 'MULTIPOLYGON'; t -- the boundary of a polar cell is a polygon SELECT GeometryType(h3_cell_to_boundary_wkb(:polar)::geometry) LIKE 'POLYGON'; t -- check num points in polar cell boundary SELECT ST_NPoints(h3_cell_to_boundary_geometry(:polar)) = 11; t -- check latitude of antimeridian crossing points SET h3.split_antimeridian TO true; SELECT every(ABS(ST_Y(p) - :lat1) < :epsilon OR ABS(ST_Y(p) - :lat2) < :epsilon) FROM ( SELECT (dp).geom AS p FROM ( (SELECT ST_DumpPoints(h3_cell_to_boundary_wkb(:edgecross)::geometry) AS dp) ) AS q1 ) AS q2 WHERE ABS(ABS(ST_X(p)) - 180) < :epsilon; t -- -- Test h3_cells_to_multi_polygon_wkb -- -- polygon is split in 4 polygons with 1 hole each WITH split AS ( SELECT h3_cells_to_multi_polygon_wkb( array(SELECT h3_polygon_to_cells(:transmeridianWithHoles, 4)))::geometry AS multi), dp AS (SELECT ST_Dump(multi) AS dp FROM split), polygons AS (SELECT (dp).geom AS geom FROM dp) SELECT GeometryType(geom) LIKE 'POLYGON' AND ST_NumInteriorRings(geom) = 1 FROM polygons; t t t t -- multipolygon with 2 polygons becomes multipolygon with 3 WITH split AS ( SELECT h3_cells_to_multi_polygon_wkb( array(SELECT h3_polygon_to_cells(:transmeridianMulti, 3)))::geometry AS multi), dp AS (SELECT ST_Dump(multi) AS dp FROM split) SELECT COUNT(*) = 3 FROM dp; t -- h3_polygon_to_cells_experimental SELECT COUNT(*) = 48 FROM ( SELECT h3_polygon_to_cells_experimental(:with2holes, 10, 'center') ) q; t SELECT COUNT(*) = 76 FROM ( SELECT h3_polygon_to_cells_experimental(:with2holes, 10, 'overlapping') ) q; t h3-pg-4.2.2/h3_postgis/test/expected/rasters.out000066400000000000000000000130161475234715600215320ustar00rootroot00000000000000\pset tuples_only on \set resolution 9 \set coverage_size 2 \set raster_size 25 \set pixel_size 0.0005 \set value_num 5 \set lat 51.5 \set lng -0.025 CREATE TABLE h3_test_rasters (id SERIAL, rast raster); INSERT INTO h3_test_rasters (rast) ( WITH vals AS ( SELECT array_agg(row) AS vals FROM ( SELECT array_agg((x + y) % :value_num + 1) AS row FROM generate_series(1, :raster_size) AS x, generate_series(1, :raster_size) AS y GROUP BY y ) t), rasts AS ( SELECT ST_AddBand( ST_MakeEmptyCoverage( :raster_size, :raster_size, :raster_size * :coverage_size, :raster_size * :coverage_size, :lng, :lat, :pixel_size, -(:pixel_size), 0, 0, 4326), ARRAY[ROW(1, '8BUI', 1, 0)]::addbandarg[] ) AS rast) SELECT ST_SetValues(r.rast, 1, 1, 1, v.vals) FROM rasts r, vals v ); CREATE FUNCTION h3_test_equal( v1 double precision, v2 double precision) RETURNS boolean AS $$ SELECT ABS(v1 - v2) < 1e-12; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE FUNCTION h3_test_raster_summary_stats_equal( s1 h3_raster_summary_stats, s2 h3_raster_summary_stats) RETURNS boolean AS $$ SELECT s1 IS NOT NULL AND s2 IS NOT NULL AND h3_test_equal((s1).count, (s2).count) AND h3_test_equal((s1).sum, (s2).sum) AND h3_test_equal((s1).mean, (s2).mean) AND h3_test_equal((s1).stddev, (s2).stddev) AND h3_test_equal((s1).min, (s2).min) AND h3_test_equal((s1).max, (s2).max); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE FUNCTION h3_test_raster_class_summary_item_equal( i1 h3_raster_class_summary_item, i2 h3_raster_class_summary_item) RETURNS boolean AS $$ SELECT i1 IS NOT NULL AND i2 IS NOT NULL AND h3_test_equal((i1).val, (i2).val) AND h3_test_equal((i1).count, (i2).count) AND h3_test_equal((i1).area, (i2).area); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; -- Results of `h3_raster_summary_clip` and `h3_raster_summary_centroids` -- should be identical WITH clip AS ( SELECT h3, h3_raster_summary_stats_agg(stats) AS stats FROM ( -- h3, stats SELECT (h3_raster_summary_clip(rast, :resolution)).* FROM h3_test_rasters ) t GROUP BY 1), centroids AS ( SELECT h3, h3_raster_summary_stats_agg(stats) AS stats FROM ( -- h3, stats SELECT (h3_raster_summary_centroids(rast, :resolution)).* FROM h3_test_rasters ) t GROUP BY 1) SELECT COUNT(*) FROM clip a FULL OUTER JOIN centroids b ON a.h3 = b.h3 WHERE NOT h3_test_raster_summary_stats_equal(a.stats, b.stats); 0 -- Results of `h3_raster_class_summary_clip` and `h3_raster_class_summary_clip` -- should be identical WITH clip AS ( SELECT h3, val, h3_raster_class_summary_item_agg(summary) AS summary FROM ( -- h3, val, summary SELECT (h3_raster_class_summary_clip(rast, :resolution)).* FROM h3_test_rasters ) t GROUP BY 1, 2), centroids AS ( SELECT h3, val, h3_raster_class_summary_item_agg(summary) AS summary FROM ( -- h3, val, summary SELECT (h3_raster_class_summary_centroids(rast, :resolution)).* FROM h3_test_rasters ) t GROUP BY 1, 2) SELECT COUNT(*) FROM clip a FULL OUTER JOIN centroids b ON a.h3 = b.h3 AND a.val = b.val WHERE NOT h3_test_raster_class_summary_item_equal(a.summary, b.summary); 0 -- Stats aggregation check: -- stats for a cell intersecting multiple rasters (with aggregation) should be -- the same when calculated on a union of rasters (without aggregation). WITH rast AS ( -- Union all test rasters SELECT ST_Union(rast) AS rast FROM h3_test_rasters), middle AS ( -- Find an H3 cell in a bottom-right corner of a first raster -- (intersecting 4 rasters) SELECT h3_lat_lng_to_cell( ST_MakePoint( ST_RasterToWorldCoordX(rast, :raster_size), ST_RasterToWorldCoordY(rast, :raster_size)), :resolution ) AS h3 FROM rast), summary1 AS ( -- Get summary from combined raster SELECT t.stats FROM ( -- h3, stats SELECT (h3_raster_summary_clip(rast, :resolution)).* FROM rast ) t, middle m WHERE t.h3 = m.h3), summary2 AS ( -- Get aggregates summary from separate rasters SELECT h3_raster_summary_stats_agg(t.stats) AS stats FROM ( -- h3, stats SELECT (h3_raster_summary_clip(rast, :resolution)).* FROM h3_test_rasters ) t, middle m WHERE t.h3 = m.h3 GROUP BY t.h3) SELECT h3_test_raster_summary_stats_equal(s1.stats, s2.stats) FROM summary1 s1, summary2 s2; t DROP FUNCTION h3_test_raster_class_summary_item_equal( h3_raster_class_summary_item, h3_raster_class_summary_item); DROP FUNCTION h3_test_raster_summary_stats_equal( h3_raster_summary_stats, h3_raster_summary_stats); DROP FUNCTION h3_test_equal(double precision, double precision); DROP TABLE h3_test_rasters; h3-pg-4.2.2/h3_postgis/test/sql/000077500000000000000000000000001475234715600163135ustar00rootroot00000000000000h3-pg-4.2.2/h3_postgis/test/sql/postgis.sql000066400000000000000000000207101475234715600205240ustar00rootroot00000000000000\pset tuples_only on -- Variables for testing \set resolution 10 \set hexagon '\'8a63a9a99047fff\'' \set meter ST_SetSRID(ST_Point(6196902.235389061,1413172.0833316022), 3857) \set degree ST_SetSRID(ST_Point(55.6677199224442,12.592131261648213), 4326) \set edgecross '\'8003fffffffffff\'::h3index' \set polar '\'81f2bffffffffff\'::h3index' \set lat1 84.76455330449812 \set lat2 89.980298101841 \set epsilon 0.0000000000001 \set longPathEndpoint1 '\'842ca2bffffffff\'::h3index' \set longPathEndpoint2 '\'842e611ffffffff\'::h3index' -- polygon with 2 holes \set with2holes '\'POLYGON((31.6520834 68.9912098,31.6521263 68.9910944,31.6530919 68.9912021,31.6540789 68.9912944,31.6550016 68.991279,31.6553449 68.9910175,31.6554737 68.9907713,31.6559458 68.990402,31.6563535 68.9900789,31.6568684 68.9897404,31.6569543 68.9895711,31.6567182 68.988671,31.6569114 68.9881786,31.6571045 68.9879401,31.6567611 68.9875708,31.6570401 68.9873015,31.6576195 68.986986,31.6581774 68.9868398,31.6584992 68.9870553,31.6586065 68.9872707,31.6590786 68.9873246,31.6594219 68.9872091,31.6596579 68.9870091,31.6596579 68.9867706,31.6601515 68.9866167,31.6607308 68.9864551,31.660409 68.986232,31.6601729 68.9860627,31.6605806 68.9859319,31.6614818 68.9859011,31.6620183 68.9857087,31.6622972 68.9854471,31.6628337 68.9852932,31.6633701 68.9852855,31.663928 68.9855702,31.6640782 68.9859088,31.6636705 68.9862166,31.6639924 68.9864859,31.664443 68.9868629,31.664679 68.9872091,31.6642928 68.9873784,31.6641426 68.9876939,31.6650009 68.9879016,31.6652155 68.9881555,31.6653657 68.9883709,31.6659665 68.9886941,31.6662884 68.9889941,31.666739 68.989248,31.6669321 68.9891095,31.6670394 68.9888787,31.6681123 68.9889326,31.6687345 68.989325,31.6692495 68.9895865,31.6701937 68.9897635,31.6710949 68.9897404,31.6725325 68.9897558,31.6733479 68.9898558,31.6743135 68.9904097,31.674571 68.990702,31.6747641 68.9909328,31.6745066 68.9911405,31.6738844 68.9912636,31.6731977 68.9914175,31.6734981 68.9916098,31.6739487 68.9915713,31.6744852 68.9915175,31.6750431 68.9914021,31.6751718 68.9911944,31.6752147 68.9910405,31.6756439 68.9910636,31.6765451 68.9912021,31.6777253 68.9912944,31.6784119 68.9912482,31.6790771 68.9911559,31.6793346 68.9913021,31.6787553 68.9916713,31.678133 68.9920867,31.6780472 68.9924483,31.6782617 68.9927098,31.6792702 68.9941098,31.6794419 68.9943636,31.6801715 68.9945328,31.6817808 68.9946328,31.6825533 68.9948174,31.6827249 68.995202,31.683712 68.9957404,31.6840124 68.9962634,31.684699 68.9965556,31.6848492 68.9968171,31.6841197 68.9969632,31.6831326 68.9969479,31.6827464 68.9969171,31.6824031 68.9968556,31.6821456 68.9967248,31.6813302 68.9968248,31.6810083 68.9971094,31.6806865 68.9971863,31.6802358 68.9971401,31.6792702 68.9967018,31.6787553 68.9963864,31.6780901 68.9958327,31.6777038 68.9956788,31.6766095 68.9955635,31.6762233 68.9954943,31.6759658 68.9952943,31.6753649 68.9951481,31.6746354 68.9952712,31.6735625 68.9951866,31.6728329 68.9951866,31.6726398 68.9953943,31.6719746 68.9955173,31.6709661 68.9954173,31.6704941 68.9951558,31.6700434 68.9948251,31.669743 68.9944867,31.6695285 68.994179,31.6693783 68.9939329,31.6690779 68.993756,31.6680479 68.9935867,31.663692 68.9929329,31.6628551 68.9927637,31.661675 68.9927637,31.6610527 68.9929637,31.6605377 68.9929714,31.6599583 68.9929406,31.6588855 68.9928175,31.658349 68.992656,31.657598 68.9923714,31.6567826 68.9922329,31.6552162 68.9920714,31.6541648 68.9919175,31.6531348 68.9916483,31.6523838 68.9914175,31.6520834 68.9912098),(31.657185 68.990202,31.657244 68.9903482,31.6574585 68.9903809,31.6578555 68.9903597,31.6581452 68.9902539,31.6583651 68.9900924,31.6582364 68.9899366,31.6578716 68.9898616,31.6575766 68.9898731,31.6573083 68.9899808,31.6572332 68.9900731,31.657185 68.990202),(31.6590196 68.9886094,31.6591644 68.9887229,31.6594058 68.9888537,31.6595936 68.9889557,31.6596794 68.9890441,31.6597116 68.9891345,31.659776 68.9892519,31.6599476 68.9891903,31.660012 68.9890614,31.6601139 68.9889383,31.6602641 68.9888326,31.6602749 68.988721,31.6601998 68.988596,31.6600603 68.988369,31.6601676 68.9882709,31.6604251 68.9882844,31.6607255 68.9882728,31.6609454 68.9881767,31.6605592 68.9880901,31.6604841 68.9879689,31.6603607 68.9878497,31.660071 68.9878843,31.6597867 68.9879959,31.6593575 68.9880305,31.6592127 68.9881132,31.6592234 68.9882421,31.6592127 68.9883786,31.6590196 68.9886094))\''::geometry(POLYGON) -- S-shaped polygon with 4 holes crossing antimeridian \set transmeridianWithHoles '\'POLYGON((-170 12.5, 170 12.5, 170 7.5, -175 7.5, -175 2.5, 170 2.5, 170 -12.5, -170 -12.5, -170 -7.5, 175 -7.5, 175 -2.5, -170 -2.5, -170 12.5), (-176 11.5, -179 11.5, -179 8.5, -176 8.5, -176 11.5), (174 11.5, 171 11.5, 171 8.5, 174 8.5, 174 11.5), (174 -3.5, 171 -3.5, 171 -6.5, 174 -6.5, 174 -3.5),(-176 -8.5, -179 -8.5, -179 -11.5, -176 -11.5, -176 -8.5))\''::geometry(POLYGON) -- multipolygon with 2 polygons: one crossing and one not crossing antimeridian \set transmeridianMulti '\'MULTIPOLYGON(((-175 50, -175 55, 175 55, 175 50, -175 50)), ((170 50, 170 55, 165 55, 165 50, 170 50)))\''::geometry(MULTIPOLYGON) SELECT h3_lat_lng_to_cell(:degree, :resolution) = '8a63a9a99047fff'; -- meters are NOT reprojected SELECT h3_lat_lng_to_cell(:meter, :resolution) <> '8a63a9a99047fff'; -- check back/forth conversion return same hex SELECT h3_lat_lng_to_cell(h3_cell_to_geometry(:hexagon), :resolution) = '8a63a9a99047fff'; -- check num points in boundary SELECT ST_NPoints(h3_cell_to_boundary_geometry(:hexagon)) = 7; -- test strict h3_lat_lng_to_cell throws for bad latlon CREATE FUNCTION h3_test_postgis_nounit() RETURNS boolean LANGUAGE PLPGSQL AS $$ BEGIN PERFORM h3_lat_lng_to_cell(POINT(360, 2.592131261648213), 1); RETURN false; EXCEPTION WHEN OTHERS THEN RETURN true; END; $$; SET h3.strict TO true; SELECT h3_test_postgis_nounit(); SET h3.strict TO false; DROP FUNCTION h3_test_postgis_nounit; -- Test wraparound \set lon 55.6677199224442 \set lat 12.592131261648213 SELECT h3_lat_lng_to_cell(POINT(:lon, :lat), 7) = h3_lat_lng_to_cell(POINT(:lon + 360, :lat), 7); SELECT h3_lat_lng_to_cell(POINT(:lon, :lat ), 7) = h3_lat_lng_to_cell(POINT(:lon, :lat + 360), 7); -- test h3_grid_path_cells_recursive works for long path SELECT COUNT(*) > 0 FROM ( SELECT h3_grid_path_cells_recursive(:longPathEndpoint1, :longPathEndpoint1) ) q; -- h3_polygon_to_cells works for polygon with two holes SELECT COUNT(*) = 48 FROM ( SELECT h3_polygon_to_cells(:with2holes, 10) ) q; -- -- Test h3_cell_to_boundary_wkb -- -- polyfill of geo boundary returns original index SELECT h3_polygon_to_cells(h3_cell_to_boundary(:hexagon)::geometry::polygon, null, :resolution) = :hexagon; -- the boundary of a non-edgecrossing index is a polygon SELECT GeometryType(h3_cell_to_boundary_wkb(:hexagon)::geometry) LIKE 'POLYGON'; -- the boundary of an edgecrossing index is a multipolygon when split SELECT GeometryType(h3_cell_to_boundary_wkb(:edgecross)::geometry) LIKE 'MULTIPOLYGON'; -- the boundary of a polar cell is a polygon SELECT GeometryType(h3_cell_to_boundary_wkb(:polar)::geometry) LIKE 'POLYGON'; -- check num points in polar cell boundary SELECT ST_NPoints(h3_cell_to_boundary_geometry(:polar)) = 11; -- check latitude of antimeridian crossing points SET h3.split_antimeridian TO true; SELECT every(ABS(ST_Y(p) - :lat1) < :epsilon OR ABS(ST_Y(p) - :lat2) < :epsilon) FROM ( SELECT (dp).geom AS p FROM ( (SELECT ST_DumpPoints(h3_cell_to_boundary_wkb(:edgecross)::geometry) AS dp) ) AS q1 ) AS q2 WHERE ABS(ABS(ST_X(p)) - 180) < :epsilon; -- -- Test h3_cells_to_multi_polygon_wkb -- -- polygon is split in 4 polygons with 1 hole each WITH split AS ( SELECT h3_cells_to_multi_polygon_wkb( array(SELECT h3_polygon_to_cells(:transmeridianWithHoles, 4)))::geometry AS multi), dp AS (SELECT ST_Dump(multi) AS dp FROM split), polygons AS (SELECT (dp).geom AS geom FROM dp) SELECT GeometryType(geom) LIKE 'POLYGON' AND ST_NumInteriorRings(geom) = 1 FROM polygons; -- multipolygon with 2 polygons becomes multipolygon with 3 WITH split AS ( SELECT h3_cells_to_multi_polygon_wkb( array(SELECT h3_polygon_to_cells(:transmeridianMulti, 3)))::geometry AS multi), dp AS (SELECT ST_Dump(multi) AS dp FROM split) SELECT COUNT(*) = 3 FROM dp; -- h3_polygon_to_cells_experimental SELECT COUNT(*) = 48 FROM ( SELECT h3_polygon_to_cells_experimental(:with2holes, 10, 'center') ) q; SELECT COUNT(*) = 76 FROM ( SELECT h3_polygon_to_cells_experimental(:with2holes, 10, 'overlapping') ) q;h3-pg-4.2.2/h3_postgis/test/sql/rasters.sql000066400000000000000000000130041475234715600205150ustar00rootroot00000000000000\pset tuples_only on \set resolution 9 \set coverage_size 2 \set raster_size 25 \set pixel_size 0.0005 \set value_num 5 \set lat 51.5 \set lng -0.025 CREATE TABLE h3_test_rasters (id SERIAL, rast raster); INSERT INTO h3_test_rasters (rast) ( WITH vals AS ( SELECT array_agg(row) AS vals FROM ( SELECT array_agg((x + y) % :value_num + 1) AS row FROM generate_series(1, :raster_size) AS x, generate_series(1, :raster_size) AS y GROUP BY y ) t), rasts AS ( SELECT ST_AddBand( ST_MakeEmptyCoverage( :raster_size, :raster_size, :raster_size * :coverage_size, :raster_size * :coverage_size, :lng, :lat, :pixel_size, -(:pixel_size), 0, 0, 4326), ARRAY[ROW(1, '8BUI', 1, 0)]::addbandarg[] ) AS rast) SELECT ST_SetValues(r.rast, 1, 1, 1, v.vals) FROM rasts r, vals v ); CREATE FUNCTION h3_test_equal( v1 double precision, v2 double precision) RETURNS boolean AS $$ SELECT ABS(v1 - v2) < 1e-12; $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE FUNCTION h3_test_raster_summary_stats_equal( s1 h3_raster_summary_stats, s2 h3_raster_summary_stats) RETURNS boolean AS $$ SELECT s1 IS NOT NULL AND s2 IS NOT NULL AND h3_test_equal((s1).count, (s2).count) AND h3_test_equal((s1).sum, (s2).sum) AND h3_test_equal((s1).mean, (s2).mean) AND h3_test_equal((s1).stddev, (s2).stddev) AND h3_test_equal((s1).min, (s2).min) AND h3_test_equal((s1).max, (s2).max); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; CREATE FUNCTION h3_test_raster_class_summary_item_equal( i1 h3_raster_class_summary_item, i2 h3_raster_class_summary_item) RETURNS boolean AS $$ SELECT i1 IS NOT NULL AND i2 IS NOT NULL AND h3_test_equal((i1).val, (i2).val) AND h3_test_equal((i1).count, (i2).count) AND h3_test_equal((i1).area, (i2).area); $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; -- Results of `h3_raster_summary_clip` and `h3_raster_summary_centroids` -- should be identical WITH clip AS ( SELECT h3, h3_raster_summary_stats_agg(stats) AS stats FROM ( -- h3, stats SELECT (h3_raster_summary_clip(rast, :resolution)).* FROM h3_test_rasters ) t GROUP BY 1), centroids AS ( SELECT h3, h3_raster_summary_stats_agg(stats) AS stats FROM ( -- h3, stats SELECT (h3_raster_summary_centroids(rast, :resolution)).* FROM h3_test_rasters ) t GROUP BY 1) SELECT COUNT(*) FROM clip a FULL OUTER JOIN centroids b ON a.h3 = b.h3 WHERE NOT h3_test_raster_summary_stats_equal(a.stats, b.stats); -- Results of `h3_raster_class_summary_clip` and `h3_raster_class_summary_clip` -- should be identical WITH clip AS ( SELECT h3, val, h3_raster_class_summary_item_agg(summary) AS summary FROM ( -- h3, val, summary SELECT (h3_raster_class_summary_clip(rast, :resolution)).* FROM h3_test_rasters ) t GROUP BY 1, 2), centroids AS ( SELECT h3, val, h3_raster_class_summary_item_agg(summary) AS summary FROM ( -- h3, val, summary SELECT (h3_raster_class_summary_centroids(rast, :resolution)).* FROM h3_test_rasters ) t GROUP BY 1, 2) SELECT COUNT(*) FROM clip a FULL OUTER JOIN centroids b ON a.h3 = b.h3 AND a.val = b.val WHERE NOT h3_test_raster_class_summary_item_equal(a.summary, b.summary); -- Stats aggregation check: -- stats for a cell intersecting multiple rasters (with aggregation) should be -- the same when calculated on a union of rasters (without aggregation). WITH rast AS ( -- Union all test rasters SELECT ST_Union(rast) AS rast FROM h3_test_rasters), middle AS ( -- Find an H3 cell in a bottom-right corner of a first raster -- (intersecting 4 rasters) SELECT h3_lat_lng_to_cell( ST_MakePoint( ST_RasterToWorldCoordX(rast, :raster_size), ST_RasterToWorldCoordY(rast, :raster_size)), :resolution ) AS h3 FROM rast), summary1 AS ( -- Get summary from combined raster SELECT t.stats FROM ( -- h3, stats SELECT (h3_raster_summary_clip(rast, :resolution)).* FROM rast ) t, middle m WHERE t.h3 = m.h3), summary2 AS ( -- Get aggregates summary from separate rasters SELECT h3_raster_summary_stats_agg(t.stats) AS stats FROM ( -- h3, stats SELECT (h3_raster_summary_clip(rast, :resolution)).* FROM h3_test_rasters ) t, middle m WHERE t.h3 = m.h3 GROUP BY t.h3) SELECT h3_test_raster_summary_stats_equal(s1.stats, s2.stats) FROM summary1 s1, summary2 s2; DROP FUNCTION h3_test_raster_class_summary_item_equal( h3_raster_class_summary_item, h3_raster_class_summary_item); DROP FUNCTION h3_test_raster_summary_stats_equal( h3_raster_summary_stats, h3_raster_summary_stats); DROP FUNCTION h3_test_equal(double precision, double precision); DROP TABLE h3_test_rasters; h3-pg-4.2.2/include/000077500000000000000000000000001475234715600140765ustar00rootroot00000000000000h3-pg-4.2.2/include/CMakeLists.txt000066400000000000000000000004311475234715600166340ustar00rootroot00000000000000add_library(postgresql_h3_shared OBJECT error.c ) target_link_libraries(postgresql_h3_shared PRIVATE PostgreSQL::PostgreSQL ) target_include_directories(postgresql_h3_shared INTERFACE ./ ) set_target_properties(postgresql_h3_shared PROPERTIES POSITION_INDEPENDENT_CODE True) h3-pg-4.2.2/include/constants.h000066400000000000000000000002041475234715600162570ustar00rootroot00000000000000#ifndef H3_CONSTANTS_H #define H3_CONSTANTS_H #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #endif /* H3_CONSTANTS_H */ h3-pg-4.2.2/include/deprecate.h000066400000000000000000000023751475234715600162120ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #include #include // PG_FUNCTION_INFO_V1 /* Inspired by PostGIS legacy function handling */ /* https://github.com/postgis/postgis/blob/master/postgis/postgis_legacy.c */ #define H3_DEPRECATE(version, funcname) \ PGDLLEXPORT PG_FUNCTION_INFO_V1(funcname); \ Datum funcname(PG_FUNCTION_ARGS) \ { \ ereport(ERROR, (\ errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \ errmsg("A stored procedure tried to use deprecated C function '%s'", \ __func__), \ errdetail("Library function '%s' was deprecated in h3 %s", \ __func__, version), \ errhint("Consider running: ALTER EXTENSION h3 UPDATE") \ )); \ PG_RETURN_POINTER(NULL); \ } h3-pg-4.2.2/include/error.c000066400000000000000000000003751475234715600154000ustar00rootroot00000000000000#include void h3_assert(int error) { if (error) ereport(ERROR, ( errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), errmsg("error code: %i", error), errhint("https://h3geo.org/docs/library/errors#table-of-error-codes"))); } h3-pg-4.2.2/include/error.h000066400000000000000000000024771475234715600154120ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #ifndef H3_ERROR_H #define H3_ERROR_H void h3_assert(int error); #define ASSERT(condition, code, msg, ...) \ if (0 == (condition)) ereport(ERROR, ( \ errcode(code), \ errmsg(msg, ##__VA_ARGS__) \ )) #define ENSURE_TYPEFUNC_COMPOSITE(x) \ ASSERT( \ x == TYPEFUNC_COMPOSITE, \ ERRCODE_INVALID_PARAMETER_VALUE, \ "Function returning record called in context " \ "that cannot accept type record" \ ) #define H3_DEPRECATION(msg) \ ereport(WARNING, ( \ errmsg("Deprecated: %s", msg) \ )) #define DEBUG(msg, ...) \ ereport(NOTICE, ( \ errmsg(msg, ##__VA_ARGS__) \ )) #define DEBUG_H3INDEX(h3index) DEBUG("index: %lx", h3index) #endif /* H3_ERROR_H */ h3-pg-4.2.2/include/type.h000066400000000000000000000032111475234715600152250ustar00rootroot00000000000000/* * Copyright 2023 Zacharias Knudsen * * 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. */ #ifndef H3_TYPE_H #define H3_TYPE_H #include /* * DatumGetH3Index * Returns H3 index value of a datum. * * Note: this macro hides whether h3 index is pass by value or by reference. */ #ifdef USE_FLOAT8_BYVAL #define DatumGetH3Index(X) ((H3Index) (X)) #else #define DatumGetH3Index(X) (* ((H3Index *) DatumGetPointer(X))) #endif /* * H3IndexGetDatum * Returns datum representation for an H3 index. * * Note: if H3 index is pass by reference, this function returns a reference * to palloc'd space. */ #ifdef USE_FLOAT8_BYVAL #define H3IndexGetDatum(X) ((Datum) (X)) #else #define H3IndexGetDatum(X) Int64GetDatum((int64) (X)) #endif /* Macros for fetching arguments and returning results of h3 index type */ #define PG_GETARG_H3INDEX(n) DatumGetH3Index(PG_GETARG_DATUM(n)) #define PG_RETURN_H3INDEX(x) return H3IndexGetDatum(x) /* use origin resolution minus one when no resolution is given */ #define PG_GETARG_OPTIONAL_RES(n, cell, offset) \ PG_NARGS() == (n + 1) ? PG_GETARG_INT32(1) : getResolution(cell) + offset #endif /* H3_TYPE_H */ h3-pg-4.2.2/scripts/000077500000000000000000000000001475234715600141425ustar00rootroot00000000000000h3-pg-4.2.2/scripts/bundle000077500000000000000000000001601475234715600153360ustar00rootroot00000000000000#!/usr/bin/env bash git clean -xdf cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --target=pgxn h3-pg-4.2.2/scripts/develop000077500000000000000000000003021475234715600155210ustar00rootroot00000000000000#!/usr/bin/env bash cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build sudo cmake --install build --component h3-pg ctest --test-dir build --output-on-failure --build-config Release h3-pg-4.2.2/scripts/documentation000077500000000000000000000006101475234715600167360ustar00rootroot00000000000000#!/usr/bin/env bash # ensure we are in script dir cd "$(dirname "$0")" # go to python project dir cd sql2doc # ensure dependencies are present poetry install # generate markdown from sql files poetry run -q -- python generate.py \ -g "API Reference" "../../h3/sql/install/*.sql" \ -g "PostGIS Integration" "../../h3_postgis/sql/install/*.sql" \ > "../../docs/api.md" h3-pg-4.2.2/scripts/release000077500000000000000000000020321475234715600155050ustar00rootroot00000000000000#!/usr/bin/env bash set -u VERSION="$1" : "$VERSION" # Version number should be changed in root `CMakeLists.txt`. sed -i -r "s/( VERSION )[0-9\.]+/\1$VERSION/g" CMakeLists.txt # Set `INSTALL_VERSION` to "${PROJECT_VERSION}". sed -i '/set(INSTALL_VERSION "${PROJECT_VERSION}")/s/^#//g' CMakeLists.txt sed -i '/set(INSTALL_VERSION "unreleased")/s/^/#/g' CMakeLists.txt # Update files (and cmake references) suffixed `--unreleased` should be renamed. rename unreleased "$VERSION" h3/sql/updates/*--unreleased.sql sed -i "s/unreleased/$VERSION/g" h3/sql/updates/*--$VERSION.sql sed -i "s/unreleased/$VERSION/g" h3/CMakeLists.txt rename unreleased "$VERSION" h3_postgis/sql/updates/*--unreleased.sql sed -i "s/unreleased/$VERSION/g" h3_postgis/sql/updates/*--$VERSION.sql sed -i "s/unreleased/$VERSION/g" h3_postgis/CMakeLists.txt git checkout -b "release-$VERSION" echo "Remember to": echo " - Installer \`.sql\` files should have \`@ availability\` comments updated." echo " - Update changelog by moving from \`Unreleased\` to a new section"h3-pg-4.2.2/scripts/sql2doc/000077500000000000000000000000001475234715600155115ustar00rootroot00000000000000h3-pg-4.2.2/scripts/sql2doc/Pipfile000066400000000000000000000001711475234715600170230ustar00rootroot00000000000000[[source]] name = "pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] [packages] lark-parser = "*" h3-pg-4.2.2/scripts/sql2doc/generate.py000066400000000000000000000317531475234715600176660ustar00rootroot00000000000000""" The concept: We don't want to manually keep API docs in sync with SQL files. So instead we should generate the docs from the sql/install/*.sql files. We have the following things to document: * Type * Cast (put in same section as the type) * Operator * Function * Aggregate function Some functions do not need docs, we can signal this by not applying a comment. This is internal operator functions for example. We should parse the sql files, extract the above, and then document all things that have comments applied. We should probably hardcode a list of things that should not be documented, so we can fail if new function are undocumented. """ import argparse import glob import re from pathlib import Path from lark import Lark, Transformer, v_args, visitors import sys #TEST # see PGXN::API::Indexer::_clean_html_body def to_anchor(text): text = re.sub(r"[^\w\s\(\)-]", "", text) # approximating processing done in Text::Markdown text = re.sub(r"^([^a-zA-Z])", r"L\1", text) text = re.sub(r"[^a-zA-Z0-9_:.-]+", ".", text) return "#" + text def md_heading(text, level=0): return "#" * (level + 1) + " " + text class Context: level = 0 def __init__(self, statements_by_refid={}): self.statements_by_refid = statements_by_refid self.level = 0 class StmtBase: level = 0 newline_before = True newline_after = True decorators = {} def __init__(self, level=-1): self.level = level def set_decorators(self, decorators): self.decorators = decorators def get_decorator(self, key): return self.decorators[key] if self.has_decorator(key) else None def has_decorator(self, key): return key in self.decorators def is_internal(self): return self.has_decorator("internal") def is_deprecated(self): return self.has_decorator("deprecated") def is_visible(self): return not self.is_internal() and not self.is_deprecated() def get_refid(self): return self.get_decorator("refid") def get_ref_text(self): return str(self) def get_anchor(self): if not self.is_visible() or self.level < 0: return None return to_anchor(str(self)) def get_refs(self, statements_by_refid): ids = self.get_decorator("ref") or [] return [statements_by_refid[refid] for refid in ids if refid in statements_by_refid] def to_ref(self): return '{}'.format(self.get_anchor(), self.get_ref_text()) def to_md(self, context): if not self.is_visible(): return "" md = str(self) if self.level > -1: md = md_heading(md, self.level) if self.newline_before: md = "\n" + md # availability availability = self.get_decorator("availability") if availability: md += f"\n*Since v{availability}*" # references refs = self.get_refs(context.statements_by_refid) if len(refs) > 0: md += "\n\nSee also: " md += ", ".join([ref.to_ref() for ref in refs]) if self.newline_after: md += "\n" return md def __str__(self): raise Execepion("Not implemented") class Argument: def __init__(self, argmode, name, argtype, default): self.argmode = argmode self.name = name self.argtype = argtype self.default = default def get_typestr(self): s = "`{}`".format(self.argtype) if self.default: s = "[{} = {}]".format(s, self.default) return s def __str__(self): s = "" if self.argmode: s += self.argmode + " " if self.name: s += self.name + " " s += "`{}`".format(self.argtype) if self.default: s = "[{} = {}]".format(s, self.default) return s class Column: def __init__(self, name, coltype): self.name = name; self.coltype = coltype; def __str__(self): return "{} `{}`".format(self.name, self.coltype) class FunctionReturnsBase: pass class FunctionReturns(FunctionReturnsBase): def __init__(self, rettype): self.rettype = rettype def __str__(self): return "`{}`".format(self.rettype) class FunctionReturnsSet(FunctionReturns): def __str__(self): return "SETOF " + super().__str__() class FunctionReturnsTable(FunctionReturnsBase): def __init__(self, columns): self.columns = columns def __str__(self): return "TABLE (" + ", ".join([str(col) for col in self.columns]) + ")"; class CustomMd(StmtBase): def __init__(self, line): super().__init__() self.newline_after = False match = re.match(r"(#+)\s*(.*)", line) if match: self.newline_before = True self.level = max(0, len(match[1]) - 1) self.line = match[2] else: self.newline_before = False self.level = -1 self.line = line def update_level(self, context): if self.level > -1: context.level = max(0, self.level) def __str__(self): return self.line class CreateFunctionStmt(StmtBase): def __init__(self, name: str, arguments, returns: FunctionReturnsBase): super().__init__(2) self.name = name self.arguments = arguments or [] self.returns = returns def get_ref_text(self): return "{}({})".format( self.name, ", ".join([arg.get_typestr() for arg in self.arguments])) def __str__(self): return "{}({}) ⇒ {}".format( self.name, ", ".join([str(arg) for arg in self.arguments]), self.returns) class CreateAggregateStmt(StmtBase): def __init__(self, name: str, arguments): super().__init__(2) self.name = name self.arguments = arguments or [] def get_ref_text(self): return "{}(setof {})".format( self.name, ", ".join([arg.get_typestr() for arg in self.arguments])) def __str__(self): return "{}(setof {})".format( self.name, ", ".join([str(arg) for arg in self.arguments])) class CreateTypeStmt(StmtBase): def __str__(self): return "" class CreateCastStmt(StmtBase): def __init__(self, source, target): super().__init__(2) self.source = source self.target = target def __str__(self): return "`{}` :: `{}`".format(self.source, self.target) class CreateOperatorStmt(StmtBase): def __init__(self, name, left, right): super().__init__(2) self.name = name self.left = left self.right = right def __str__(self): return "Operator: `{}` {} `{}`".format(self.left, self.name, self.right) class CreateCommentStmt(StmtBase): def __init__(self, text): super().__init__() self.text = text def __str__(self): return self.text class SQLTransformer(Transformer): def start(self, statements): # flatten flat = [item for sublist in statements for item in sublist] return flat @v_args(inline=True) def custom_decorated_statement(self, decorators, statement = None): if not statement: raise visitors.Discard() if decorators: statement.set_decorators(decorators) return statement def custom_decorators(self, lines): decorators = {} for line in lines: try: key, value = line.split(":") key = str(key).lower() if key in ["ref"]: decorators[key] = [v.strip() for v in value.split(",")] else: decorators[key] = value.strip() except: decorators[str(line).lower()] = True return decorators def custom_md_statement(self, children): return children # -- MARKDOWN -------------------------------------------------------------- @v_args(inline=True) def custom_markdown(self, line=""): line = re.sub(r"^--\|[ \t]?", '', line) return CustomMd(line) # -- CREATE TYPE ----------------------------------------------------------- def create_type_stmt(self, children): return CreateTypeStmt() # -- CREATE CAST ----------------------------------------------------------- @v_args(inline=True) def create_cast_stmt(self, source, target, *children): return CreateCastStmt(source, target) # -- CREATE OPERATOR ------------------------------------------------------- @v_args(inline=True) def create_oper_stmt(self, name, options): return CreateOperatorStmt(name, options['LEFTARG'], options['RIGHTARG']) def create_oper_opts(self, opts): return {key: value for [key, value] in opts} @v_args(inline=True) def create_oper_opt(self, option, value=None): return [str(option), value] # -- CREATE FUNCTION ------------------------------------------------------- @v_args(inline=True) def create_fun_ret_table_columns(self, columns): return FunctionReturnsTable(columns) def create_fun_rettype(self, children): rettype = children[1] if children[0] is not None: return FunctionReturnsSet(rettype) return FunctionReturns(rettype) def column_list(self, children): return children @v_args(inline=True) def column(self, name: str, coltype: str): return Column(name, coltype) @v_args(inline=True) def create_func_stmt(self, name: str, arguments, returns, *opts): # skip internal functions if name.startswith("__"): raise visitors.Discard() return CreateFunctionStmt(name, arguments, returns) # -- CREATE AGGREGATE ------------------------------------------------------ @v_args(inline=True) def create_agg_stmt(self, name: str, arguments, *params): return CreateAggregateStmt(name, arguments) # -- CREATE COMMENT -------------------------------------------------------- @v_args(inline=True) def comment_on_stmt(self, child, text): return CreateCommentStmt(text) # -- FUNCTION OR AGGREGATE ARGUMENTS --------------------------------------- def argument_list(self, children): return children @v_args(inline=True) def argument(self, argmode, name, argtype, default=None): return Argument(argmode, name, argtype, default) # -- CREATE OPERATOR CLASS ------------------------------------------------- def create_opcl_stmt(self, children): raise visitors.Discard() # -- SIMPLE RULES ---------------------------------------------------------- true = lambda self, _: "`true`" false = lambda self, _: "`false`" number = v_args(inline=True)(int) def fun_name(self, children): return children[1] @v_args(inline=True) def string(self, s): return s[1:-1].replace('\\"', '"') # -- TERMINALS ------------------------------------------------------------- def SIGNED_NUMBER(self, children): return int(children) def CNAME(self, cname): return str(cname) def OPERATOR(self, name): return str(name) def parse_args(): parser = argparse.ArgumentParser() parser.add_argument( '--group', '-g', nargs=2, metavar=('header', 'glob'), action='append', help='Globs for each group of SQL install files') return parser.parse_args() def create_parser(): here = Path(__file__).parent parser = Lark.open( here / "sql.lark", parser="lalr", maybe_placeholders=True) return parser def process_group(parser, group, statements_by_refid): [header, files_glob] = group statements = [] paths = glob.glob(files_glob) for path in sorted(paths): group_statements = parse_file(parser, path) # Add referenced statements to map for statement in group_statements: refid = statement.get_refid() if refid: statements_by_refid[refid] = statement statements += group_statements return (header, statements) # return flat list of statements in file def parse_file(parser, path): with open(path) as fd: sql = fd.read() tree = parser.parse(sql) statements = SQLTransformer(visit_tokens=True).transform(tree) return statements def statements_to_md(statements, context): items = [stmt.to_md(context) for stmt in statements] return [item for item in items if len(item) > 0] def main(): args = parse_args() parser = create_parser() groups = [] statements_by_refid = {} # Process groups for item in args.group: group = process_group(parser, item, statements_by_refid) groups.append(group) # Output md = "" for group in groups: [header, statements] = group md += md_heading(header) + "\n" md += "\n".join(statements_to_md(statements, Context(statements_by_refid))) + "\n" print(md) if __name__ == "__main__": main() h3-pg-4.2.2/scripts/sql2doc/poetry.lock000066400000000000000000000012451475234715600177070ustar00rootroot00000000000000[[package]] name = "lark-parser" version = "0.12.0" description = "a modern parsing library" category = "main" optional = false python-versions = "*" [package.extras] atomic-cache = ["atomicwrites"] nearley = ["js2py"] regex = ["regex"] [metadata] lock-version = "1.1" python-versions = "^3.10" content-hash = "13b79c3b8dcbda6768897ce2de93db42bde7daba97ed35e49cc12468f26e873d" [metadata.files] lark-parser = [ {file = "lark-parser-0.12.0.tar.gz", hash = "sha256:15967db1f1214013dca65b1180745047b9be457d73da224fcda3d9dd4e96a138"}, {file = "lark_parser-0.12.0-py2.py3-none-any.whl", hash = "sha256:0eaf30cb5ba787fe404d73a7d6e61df97b21d5a63ac26c5008c78a494373c675"}, ] h3-pg-4.2.2/scripts/sql2doc/pyproject.toml000066400000000000000000000005131475234715600204240ustar00rootroot00000000000000[tool.poetry] name = "documentation" version = "1.0.0" description = "Autogenerate postgres extension documentation from sql files" authors = ["Zacharias Knudsen "] [tool.poetry.dependencies] python = "^3.10" lark-parser = "^0.12.0" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" h3-pg-4.2.2/scripts/sql2doc/sql.lark000066400000000000000000000177421475234715600171760ustar00rootroot00000000000000// statement list start: custom_md_statement (";" custom_md_statement)* ";"? // markdown or statement custom_md_statement: (custom_markdown)* custom_decorated_statement custom_decorated_statement: [custom_decorators] statement // statement ?statement: create_type_stmt | create_cast_stmt | create_opcl_stmt | create_oper_stmt | create_func_stmt | create_agg_stmt | comment_on_stmt custom_decorators: ("--@" /([^\n])+/)+ custom_markdown: /--\|[ \t]?([^\n])+/ | "--|\n" // ----------------------------------------------------------------------------- // --------------- Basic SQL below --------------------------------------------- // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // CREATE TYPE name ... create_type_stmt: "CREATE" "TYPE" CNAME "AS"? ("(" /([^\)])+/ ")")? // ----------------------------------------------------------------------------- // CREATE CAST ... create_cast_stmt: "CREATE" "CAST" "(" DATATYPE "AS" DATATYPE ")" "WITH" "FUNCTION" CNAME "(" /([^\)])+/ ")" // ----------------------------------------------------------------------------- // CREATE OPERATOR CLASS name [ DEFAULT ] FOR TYPE data_type // USING index_method [ FAMILY family_name ] AS // { OPERATOR strategy_number operator_name [ ( op_type, op_type ) ] [ FOR SEARCH | FOR ORDER BY sort_family_name ] // | FUNCTION support_number [ ( op_type [ , op_type ] ) ] function_name ( argument_type [, ...] ) // | STORAGE storage_type // } [, ... ] create_opcl_stmt: "CREATE" "OPERATOR" "CLASS" CNAME "DEFAULT"? "FOR" "TYPE" CNAME "USING" CNAME "AS" create_opcl_list create_opcl_opts: "OPERATOR" SIGNED_NUMBER OPERATOR | "FUNCTION" SIGNED_NUMBER fun_name "(" [argument_list] ")" create_opcl_list: create_opcl_opts ("," create_opcl_opts)* // ----------------------------------------------------------------------------- // CREATE OPERATOR name ( // PROCEDURE = function_name // [, LEFTARG = left_type ] [, RIGHTARG = right_type ] // [, COMMUTATOR = com_op ] [, NEGATOR = neg_op ] // [, RESTRICT = res_proc ] [, JOIN = join_proc ] // [, HASHES ] [, MERGES ] // ) create_oper_stmt: "CREATE" "OPERATOR" OPERATOR "(" create_oper_opts ")" create_oper_opt: /PROCEDURE/ "=" CNAME | /LEFTARG/ "=" DATATYPE | /RIGHTARG/ "=" DATATYPE | /COMMUTATOR/ "=" OPERATOR | /NEGATOR/ "=" OPERATOR | /RESTRICT/ "=" CNAME | /JOIN/ "=" CNAME | /HASHES/ | /MERGES/ create_oper_opts: create_oper_opt ("," create_oper_opt)* // ----------------------------------------------------------------------------- // CREATE [ OR REPLACE ] FUNCTION // name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] ) // [ RETURNS rettype // | RETURNS TABLE ( column_name column_type [, ...] ) ] // { LANGUAGE lang_name // | TRANSFORM { FOR TYPE type_name } [, ... ] // | WINDOW // | IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF // | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT // | [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER // | COST execution_cost // | ROWS result_rows // | SET configuration_parameter { TO value | = value | FROM CURRENT } // | AS 'definition' // | AS 'obj_file', 'link_symbol' // } ... // [ WITH ( attribute [, ...] ) ] create_func_stmt: "CREATE" ("OR" "REPLACE")? "FUNCTION" fun_name "(" [argument_list] ")" [create_fun_rets] create_fun_opts* ?create_fun_rets: ("RETURNS" "TABLE" "(" create_fun_ret_table_columns ")") | ("RETURNS" create_fun_rettype) create_fun_opts: "LANGUAGE" (CNAME|"'" CNAME "'") | ("IMMUTABLE" | "STABLE" | "VOLATILE" | ("NOT"? "LEAKPROOF")) | (("CALLED" "ON" "NULL" "INPUT") | ("RETURNS" "NULL" "ON" "NULL" "INPUT") | "STRICT") | ("PARALLEL" ("UNSAFE" | "RESTRICTED" | "SAFE")) | "AS" string ("," string)? create_fun_ret_table_columns: column_list column_list: column ("," column)* argument_list: argument ("," argument)* !create_fun_rettype: ["SETOF"] DATATYPE // ----------------------------------------------------------------------------- // CREATE [ OR REPLACE ] AGGREGATE name ( [ argmode ] [ argname ] arg_data_type [ , ... ] ) ( // SFUNC = sfunc, // STYPE = state_data_type // [ , SSPACE = state_data_size ] // [ , FINALFUNC = ffunc ] // [ , FINALFUNC_EXTRA ] // [ , FINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE } ] // [ , COMBINEFUNC = combinefunc ] // [ , SERIALFUNC = serialfunc ] // [ , DESERIALFUNC = deserialfunc ] // [ , INITCOND = initial_condition ] // [ , MSFUNC = msfunc ] // [ , MINVFUNC = minvfunc ] // [ , MSTYPE = mstate_data_type ] // [ , MSSPACE = mstate_data_size ] // [ , MFINALFUNC = mffunc ] // [ , MFINALFUNC_EXTRA ] // [ , MFINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE } ] // [ , MINITCOND = minitial_condition ] // [ , SORTOP = sort_operator ] // [ , PARALLEL = { SAFE | RESTRICTED | UNSAFE } ] // ) // create_agg_stmt: "CREATE" ("OR" "REPLACE")? "AGGREGATE" fun_name "(" [argument_list] ")" "(" agg_param_list ")" create_agg_stmt: "CREATE" ("OR" "REPLACE")? "AGGREGATE" fun_name "(" [argument_list] ")" "(" agg_param_list ")" agg_param_list: agg_param ("," agg_param)* agg_param: "sfunc" "=" fun_name | "stype" "=" DATATYPE | "finalfunc" "=" fun_name | "parallel" "=" ("safe"|"restricted"|"unsafe") // ----------------------------------------------------------------------------- // COMMENT ON // { // ... // CAST (source_type AS target_type) | // ... // FUNCTION function_name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) | // ... // OPERATOR operator_name (left_type, right_type) | // ... // } IS 'text' comment_on_stmt: "COMMENT" "ON" comment_on_type "IS" string comment_on_type: "CAST" "(" DATATYPE "AS" DATATYPE ")" -> comment_on_cast | "FUNCTION" fun_name "(" [argument_list] ")" -> comment_on_function | "OPERATOR" OPERATOR "(" argument "," argument ")" -> comment_on_operator // ----------------------------------------------------------------------------- // SIMPLE RULES column: CNAME DATATYPE argument: [ARGMODE] [CNAME] DATATYPE ("DEFAULT" expr)? ARGMODE.2: "IN" | "OUT" | "INOUT" DATATYPE_SCALAR: "h3index" | "raster" | "summarystats" | "h3_raster_summary_stats" | "h3_raster_class_summary_item" | "jsonb" | "bigint" | "boolean" | "cstring" | "double" WS "precision" | "float" | "geography" | "geometry" | "bytea" | "int32" | "int8" | "integer" | "internal" | "int" | "point" | "polygon" | "record" | "text" | "void" DATATYPE: DATATYPE_SCALAR "[]"? fun_name: [CNAME "."] CNAME ?expr: atom | string atom: SIGNED_NUMBER -> number | "TRUE" -> true | "FALSE" -> false string: STRING // ----------------------------------------------------------------------------- // TERMINALS // Terminals are used to match text into symbols. // They can be defined as a combination of literals and other terminals. LITERAL: SIGNED_NUMBER | ESCAPED_STRING OPERATOR: ("+"|"-"|"*"|"/"|"<"|">"|"="|"~"|"!"|"@"|"#"|"%"|"^"|"&"|"|"|"`"|"?")+ STRING: "'" /([^'])+/ "'" | "$$" /(.|\n)*?/ "$$" MULTI_COMMENT : "/*" /(.|\n)+/ "*/" SINGLE_COMMENT: "--" /[^\|@]/ /([^\n])*/ COMMAND: "\\" /([^\n])+/ // ----------------------------------------------------------------------------- %import common (CNAME, INT, ESCAPED_STRING, _STRING_ESC_INNER, SIGNED_NUMBER, WS, NEWLINE) %ignore COMMAND %ignore MULTI_COMMENT %ignore SINGLE_COMMENT %ignore WS