diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8e3a97c4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +* +!sql +!src +!test +!entrypoint.sh +!h3.control +!Makefile diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fb470a56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# C +*.o +*.bc +*.so + +# Test results +test/regression.* +test/results + +# Generated SQL files +sql/h3--?.?.?.sql +!sql/h3--?.?.?--*.sql + +# IDEs +.vscode diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e59dbdbb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +ARG VERSION=9.6 +FROM mdillon/postgis:${VERSION} +ARG VERSION + +# Install dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + cmake \ + git \ + postgresql-server-dev-$VERSION + +# Build and install H3 (for static linking) +RUN git clone https://github.com/uber/h3.git /tmp/h3 +WORKDIR /tmp/h3 +RUN cmake -DCMAKE_C_FLAGS=-fPIC . +RUN make +RUN make install + +# hacky solution to non-root user in entrypoint.sh +RUN chmod -R a+w \ + /usr/share/postgresql/${VERSION} \ + /usr/lib/postgresql/${VERSION}/lib + +# Set up makeinstall entrypoint +COPY ./entrypoint.sh /docker-entrypoint-initdb.d/999-docker-test.sh + +# Set up directory structure including source files +COPY --chown=postgres:postgres . /tmp/h3-pg +WORKDIR /tmp/h3-pg + +CMD ["postgres", "--version"] diff --git a/LICENSE b/LICENSE new file mode 100755 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..07a7bda5 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +EXTENSION = h3 +EXTVERSION = $(shell grep default_version $(EXTENSION).control | \ + sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") + +DATA = $(wildcard sql/${EXTENSION}--*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql +OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) +MODULE_big = $(EXTENSION) +SHLIB_LINK += -lh3 + +# Testing +REGRESS = $(basename $(notdir $(wildcard test/sql/*.sql))) +REGRESS_OPTS = --inputdir=test --outputdir=test --load-extension=postgis --load-extension=h3 + +all: sql/$(EXTENSION)--$(EXTVERSION).sql + +sql/$(EXTENSION)--$(EXTVERSION).sql: $(sort $(wildcard sql/$(EXTENSION)--*--*.sql)) + cat $^ > $@ + +EXTRA_CLEAN += sql/$(EXTENSION)--$(EXTVERSION).sql test/regression.diffs test/regression.out test/results + +# PGXS boilerplate +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) diff --git a/README.md b/README.md new file mode 100644 index 00000000..a74251b7 --- /dev/null +++ b/README.md @@ -0,0 +1,778 @@ +# H3 bindings for PostgreSQL + +- [Installation](#installation) +- [Development](#development) +- [Usage](#usage) + +An extension for PostgreSQL that provides bindings for the [H3](https://uber.github.io/h3) indexing system. + +## Installation + +First, you must build [H3](https://uber.github.io/h3). + +``` +git clone https://github.com/uber/h3.git +cd h3 +cmake -DCMAKE_C_FLAGS=-fPIC . +make +sudo make install +``` + +Then build h3-pg: + +``` +git clone ... +cd h3-pg +make +sudo make install +``` + +Run `psql` and load/use extension in database: + +``` +CREATE_EXTENSION h3; + +SELECT h3_h3_to_children('880326b88dfffff', 9); + h3_h3_to_children +----------------- + 890326b88c3ffff + 890326b88c7ffff + 890326b88cbffff + 890326b88cfffff + 890326b88d3ffff + 890326b88d7ffff + 890326b88dbffff +``` + +## Development + +We provide a Dockerfile for development without installation of H3 and Postgres. The following requires that your system has `docker` installed. + +First, build the docker image: + +``` +docker build -t h3-pg . +``` + +Then, build the extension and run the test suite: + +``` +docker run --rm h3-pg +``` + +Afterwards, to quickly build and test changes, run: + +``` +chmod -R 777 . +docker run --rm -it -v "$PWD":/tmp/h3-pg h3-pg +``` + +It will mount the code as a volume, and also mount the test output directory, +so output can be inspected. The chmod might be needed if you get permission +denied errors. + +## Usage + +Generally all functions have been renamed from camelCase to snake_case with an added `h3*`prefix. This means a few functions will have a double`h3*h3*` prefix, but we chose this for consistency. For example `h3ToChildren` becomes `h3_h3_to_children`. + +### Indexing functions + +#### h3_geo_to_h3(point, resolution) returns h3index + +Converts native PostgreSQL point to hex at given resolution. + +``` +SELECT h3_geo_to_h3(POINT('64.7498111652365,89.5695822308866'), 8); + h3_geo_to_h3 +----------------- + 880326b88dfffff +(1 row) +``` + +#### h3_h3_to_geo(h3index) returns point + +Finds the centroid of this hex in native PostgreSQL point type. + +``` +SELECT h3_h3_to_geo('880326b88dfffff'); + h3_h3_to_geo +------------------------------------- + (64.7498111652365,89.5695822308866) +(1 row) +``` + +#### h3_h3_to_geo_boundary(h3index) returns polygon + +Find the boundary of this hex, in native PostgreSQL polygon type. + +``` +SELECT h3_h3_to_geo_boundary(:hexagon); + h3_h3_to_geo_boundary +----------------------------------------------------------------------------- + ((89.5656359347422,64.3352882950961),...,(89.570369702947,64.104106930976)) +(1 row) +``` + +### Index inspection functions + +#### h3_h3_get_resolution(h3index) returns integer + +Returns the resolution of this hex. + +``` +SELECT h3_h3_get_resolution(:hexagon), h3_h3_get_resolution(:pentagon); + h3_h3_get_resolution | h3_h3_get_resolution +-------------------+------------------- + 8 | 3 +(1 row) +``` + +#### h3_h3_get_base_cell(h3index) returns integer + +Returns the base cell number of the given hex. + +``` +SELECT h3_h3_get_base_cell(:hexagon), h3_h3_get_base_cell(h3_h3_to_parent(:hexagon)); + h3_h3_get_base_cell | h3_h3_get_base_cell +------------------+------------------ + 2 | 2 +(1 row) +``` + +#### h3_string_to_h3(cstring) returns h3index + +Converts the string representation to H3Index representation. Not very useful, since the internal representation can already be displayed as text for output, and read as text from input. + +``` +SELECT h3_string_to_h3('880326b88dfffff); + h3_string_to_h3 +----------------- + 880326b88dfffff +(1 row) +``` + +#### h3_h3_to_string(h3index) returns cstring + +Converts the H3Index representation of the index to the string representation. Not very useful, since the internal representation can already be displayed as text for output, and read as text from input. + +``` +SELECT h3_h3_to_string('880326b88dfffff); + h3_h3_to_string +----------------- + 880326b88dfffff +(1 row) +``` + +#### h3_h3_is_valid(h3index) returns boolean + +Returns whether this is a valid hex. + +``` +SELECT h3_h3_is_valid(:hexagon), h3_h3_is_valid(:pentagon), h3_h3_is_valid(:invalid); + h3_h3_is_valid | h3_h3_is_valid | h3_h3_is_valid +-------------+-------------+------------- + t | t | f +(1 row) + +``` + +#### h3_h3_is_res_class_iii(h3index) returns boolean + +Returns true if the resolution of the given index has a class-III rotation, +returns false if it has a class-II. + +``` +SELECT h3_h3_is_res_class_iii(:hexagon), h3_h3_is_res_class_iii(h3_h3_to_parent(:hexagon)); + h3_h3_is_res_class_iii | h3_h3_is_res_class_iii +---------------------+--------------------- + f | t +(1 row) +``` + +#### h3_h3_is_pentagon(h3index) returns boolean + +Returns whether this represents a pentagonal cell. + +``` +SELECT h3_h3_is_pentagon(:hexagon), h3_h3_is_pentagon(:pentagon); + h3_h3_is_pentagon | h3_h3_is_pentagon +----------------+---------------- + f | t +(1 row) +``` + +### Grid traversal functions + +#### h3_k_ring(h3index[, k]) returns setof h3index + +Returns all hexes within `k` (default 1) distance of the origin `hex`, including itself. + +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 provided in no particular order. + +``` +SELECT h3_k_ring('880326b88dfffff'); + h3_k_ring +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff +(7 rows) +``` + +#### h3_k_ring_distances(h3index[, k]) returns setof h3indexDistance + +Finds the set of all hexes within `k` (default 1) distance of the origin `hex` and their respective distances, including itself. + +Output rows are provided in no particular order. + +``` +SELECT * FROM h3_k_ring_distances(:hexagon); + index | distance +-----------------+---------- + 880326b88dfffff | 0 + 880326b8ebfffff | 1 + 880326b8e3fffff | 1 + 880326b885fffff | 1 + 880326b881fffff | 1 + 880326b889fffff | 1 + 880326b8c7fffff | 1 +(7 rows) +``` + +#### h3_hex_range(h3index[, k]) returns setof h3index + +Returns all hexes within `k` (default 1) distance of the origin `hex`, including itself. + +Output is sorted in increasing distance from origin. +Will report an error if it encounters a pentagon, in this case use k_ring. + +``` +SELECT h3_hex_range(:hexagon); + h3_hex_range +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff +(7 rows) +``` + +#### h3_hex_range_distances(h3index[, k]) returns setof h3indexDistance + +Finds the set of all hexes within `k` (default 1) distance of the origin `hex` and their respective distances, including itself. + +Output is sorted in increasing distance from origin. +Will report an error if it encounters a pentagon, in this case use h3_k_ring. + +``` +SELECT * FROM h3_hex_range_distances(:hexagon); + index | distance +-----------------+---------- + 880326b88dfffff | 0 + 880326b8ebfffff | 1 + 880326b8e3fffff | 1 + 880326b885fffff | 1 + 880326b881fffff | 1 + 880326b889fffff | 1 + 880326b8c7fffff | 1 +(7 rows) +``` + +#### h3_hex_ranges(ARRAY h3index[, k]) returns setof h3index + +Returns all hexes within `k` (default 1) distance of every hex in the given array, including themselves. + +Output is sorted in first by increasing order of elements in the array, secondly by distance of the particular element. +Will report an error if it encounters a pentagon. + +``` +SELECT h3_hex_range(:hexagon), h3_hex_range('880326b8ebfffff'), h3_hex_ranges('{880326b88dfffff,880326b8ebfffff}'::h3index[]); + h3_hex_range | h3_hex_range | h3_hex_ranges +-----------------+-----------------+----------------- + 880326b88dfffff | 880326b8ebfffff | 880326b88dfffff + 880326b8ebfffff | 880326b8e9fffff | 880326b8ebfffff + 880326b8e3fffff | 880326b8e1fffff | 880326b8e3fffff + 880326b885fffff | 880326b8e3fffff | 880326b885fffff + 880326b881fffff | 880326b88dfffff | 880326b881fffff + 880326b889fffff | 880326b8c7fffff | 880326b889fffff + 880326b8c7fffff | 880326b8c5fffff | 880326b8c7fffff + | | 880326b8ebfffff + | | 880326b8e9fffff + | | 880326b8e1fffff + | | 880326b8e3fffff + | | 880326b88dfffff + | | 880326b8c7fffff + | | 880326b8c5fffff +(14 rows) +``` + +#### h3_hex_ring(h3index[, k]) returns setof h3index + +Returns the hollow ring of hexes with `k` (default 1) distance of the origin `hex`. + +Will report an error if it encounters a pentagon, in this case use h3_k_ring. + +``` +SELECT h3_hex_ring(:hexagon, 2); + h3_hex_ring +----------------- + 880326b8c1fffff + 880326b8c5fffff + 880326b8e9fffff + 880326b8e1fffff + 880326b8e7fffff + 880326b8a9fffff + 880326b8abfffff + 880326b887fffff + 880326b883fffff + 880326b88bfffff + 880326b8d5fffff + 880326b8c3fffff +(12 rows) +``` + +#### h3_distance(h3index, h3index) returns int + +Determines the distance in grid cells between the two given indices. + +``` +SELECT h3_distance('880326b881fffff', '880326b885fffff'); + h3_distance +------------- + 1 +(1 row) +``` + +EXPERIMENTALS + +### Hierarchical grid functions + +#### h3_h3_to_parent(h3index[, parentRes]) returns h3index + +Returns the parent (coarser) hex containing this `hex` at given `parentRes` (if no resolution is given, parent at current resolution minus one is found). + +``` +SELECT h3_h3_to_parent('880326b88dfffff', 5); + h3_h3_to_parent +----------------- + 850326bbfffffff +(1 row) +``` + +#### h3_h3_to_children(h3index[, childRes]) returns setof h3index + +Returns all hexes contained by `hex` at given `childRes` (if no resolution is given, children at current resolution plus one is found). + +May cause problems with too large memory allocations. Please see `h3_h3_to_children_slow`. + +``` +SELECT h3_h3_to_children('880326b88dfffff', 9); + h3_h3_to_children +----------------- + 890326b88c3ffff + 890326b88c7ffff + 890326b88cbffff + 890326b88cfffff + 890326b88d3ffff + 890326b88d7ffff + 890326b88dbffff +(7 rows) +``` + +#### h3_h3_to_children_slow(h3index[, childRes]) returns setof h3index + +This functions does the same as `h3_h3_to_children_slow` but allocates smaller chunks of memory at the cost speed. + +#### h3_compact(h3index[]) returns setof h3index + +Returns the compacted version of the input array. I.e. if all children of an hex is included in the array, these will be compacted into the parent hex. + +``` +SELECT h3_compact(array_cat(ARRAY(SELECT h3_h3_to_children('880326b88dfffff')), ARRAY(SELECT h3_h3_to_children('880326b88bfffff')))); + h3_compact +----------------- + 880326b88bfffff + 880326b88dfffff +(2 rows) +``` + +#### h3_uncompact(ARRAY h3index[, resolution]) returns setof h3index + +Uncompacts the given hex array at the given resolution. If no resolution it will be chosen to be the highest resolution of the given indexes + 1. + +``` +SELECT h3_uncompact(array_cat(ARRAY(SELECT h3_h3_to_parent('880326b88dfffff')), '{880326b88bfffff}'::h3index[])); + h3_uncompact +----------------- + 890326b8803ffff + 890326b8807ffff + 890326b880bffff + 890326b880fffff + 890326b8813ffff + 890326b8817ffff + 890326b881bffff + 890326b8823ffff + 890326b8827ffff + 890326b882bffff + 890326b882fffff + 890326b8833ffff + 890326b8837ffff + 890326b883bffff + 890326b8843ffff + 890326b8847ffff + 890326b884bffff + 890326b884fffff + 890326b8853ffff + 890326b8857ffff + 890326b885bffff + 890326b8863ffff + 890326b8867ffff + 890326b886bffff + 890326b886fffff + 890326b8873ffff + 890326b8877ffff + 890326b887bffff + 890326b8883ffff + 890326b8887ffff + 890326b888bffff + 890326b888fffff + 890326b8893ffff + 890326b8897ffff + 890326b889bffff + 890326b88a3ffff + 890326b88a7ffff + 890326b88abffff + 890326b88afffff + 890326b88b3ffff + 890326b88b7ffff + 890326b88bbffff + 890326b88c3ffff + 890326b88c7ffff + 890326b88cbffff + 890326b88cfffff + 890326b88d3ffff + 890326b88d7ffff + 890326b88dbffff + 890326b88a3ffff + 890326b88a7ffff + 890326b88abffff + 890326b88afffff + 890326b88b3ffff + 890326b88b7ffff + 890326b88bbffff +(56 rows) +``` + +### Region functions + +#### h3_polyfill(exterior polygon, holes polygon[], resolution integer) returns setof h3index + +Polyfill takes a given exterior native postgres polygon and an array of interior holes (also native polygons), along with a resolutions and fills it with hexagons that are contained. + +``` +SELECT h3_polyfill(exterior, holes, 1) FROM +( + SELECT * FROM h3_h3_set_to_linked_geo(ARRAY(SELECT h3_h3_to_children('8059fffffffffff, 1))) +) q; + h3_polyfill +----------------- + 8158fffffffffff + 8159bffffffffff + 8158bffffffffff + 81597ffffffffff + 81587ffffffffff + 81593ffffffffff + 81583ffffffffff +(7 rows) +``` + +#### h3_h3_set_to_linked_geo(h3index[]) returns SET (exterior polygon, holes polygon[]) + +Create records of exteriors and holes. + +``` +SELECT h3_polyfill(exterior, holes, 1) FROM +( + SELECT * FROM h3_h3_set_to_linked_geo(ARRAY(SELECT h3_h3_to_children('8059fffffffffff, 1))) +) q; + h3_polyfill +----------------- + 8158fffffffffff + 8159bffffffffff + 8158bffffffffff + 81597ffffffffff + 81587ffffffffff + 81593ffffffffff + 81583ffffffffff +(7 rows) +``` + +### Unidirectional Edges + +Unidirectional edges are a form of H3Indexes that denote a unidirectional edge between two neighbouring indexes. + +### h3_h3_indexes_are_neighbors(h3index, h3index) returns boolean + +Determines whether or not the two indexes are neighbors. Returns true if they are and false if they aren't + +``` +SELECT h3_h3_indexes_are_neighbors(:hexagon, '880326b8ebfffff'), h3_h3_indexes_are_neighbors('880326b881fffff', '880326b8ebfffff'); + h3_h3_indexes_are_neighbors | h3_h3_indexes_are_neighbors +--------------------------+-------------------------- + t | f +(1 row) +``` + +#### h3_get_h3_unidirectional_edge(origin h3index, destination h3index) returns h3index + +Returns the edge from origin to destination + +Will error if the two indexes are not neighbouring + +``` +SELECT(h3_get_h3_unidirectional_edge(:hexagon, :neighbour)); + h3_get_h3_unidirectional_edge +---------------------------- + 1180326b885fffff +(1 row) +``` + +#### h3_h3_unidirectional_edge_is_valid(h3index) returns boolean + +Returns true if the given hex is a valid edge. + +``` +SELECT h3_h3_unidirectional_edge_is_valid(h3_get_h3_unidirectional_edge(:hexagon, :neighbour)); + h3_h3_unidirectional_edge_is_valid +--------------------------------- + t +(1 row) +``` + +#### h3_get_origin_h3_index_from_unidirectional_edge(h3index) returns h3index + +Returns the origin index from the given edge + +``` +SELECT h3_get_origin_h3_index_from_unidirectional_edge(h3_get_h3_unidirectional_edge(:hexagon, :neighbour)), :hexagon; + h3_get_origin_h3_index_from_unidirectional_edge | ?column? +----------------------------------------------+----------------- + 880326b885fffff | 880326b885fffff +(1 row) +``` + +#### h3_get_destination_h3_index_from_unidirectional_edge(h3index) returns h3index + +Returns the destination index from the given edge + +``` +SELECT h3_get_destination_h3_index_from_unidirectional_edge(h3_get_h3_unidirectional_edge(:hexagon, :neighbour)), :neighbour; + h3_get_destination_h3_index_from_unidirectional_edge | ?column? +---------------------------------------------------+----------------- + 880326b887fffff | 880326b887fffff +(1 row) +``` + +#### h3_get_h3_indexes_from_unidirectional_edge(h3index) returns edgeIndexes (origin h3index, destination h3index) + +Returns both the origin and destination indexes from the given edge + +``` +SELECT * FROM h3_get_h3_indexes_from_unidirectional_edge(h3_get_h3_unidirectional_edge(:hexagon, :neighbour)); + origin | destination +-----------------+----------------- + 880326b885fffff | 880326b887fffff +(1 row) +``` + +#### h3_get_h3_unidirectional_edges_from_hexagon(h3index) returns setof h3index + +Returns the set of all valid unidirectional edges with the given origin + +``` +SELECT h3_get_h3_unidirectional_edges_from_hexagon(:hexagon); + h3_get_h3_unidirectional_edges_from_hexagon +------------------------------------------ + 1180326b885fffff + 1280326b885fffff + 1380326b885fffff + 1480326b885fffff + 1580326b885fffff + 1680326b885fffff +(6 rows) +``` + +#### h3_get_h3_unidirectional_edge_boundary(h3index) returns polygon + +Find the boundary of this edge, in native PostgreSQL polygon type. + +``` +SELECT h3_get_unidirectional_edge_boundary(h3_get_h3_unidirectional_edge(:hexagon, :neighbour)); + h3_get_unidirectional_edge_boundary +--------------------------------------------------------------------------- + ((89.5830164946548,64.7146398954916),(89.5790678021742,64.2872231517217)) +(1 row) +``` + +### Miscellaneous + +#### h3_degs_to_rads(float) returns float + +Converts degrees to radians. + +``` +SELECT h3_rads_to_degs(degs_to_rads(90.45)); + h3_rads_to_degs +-------------- + 90.45 +(1 row) +``` + +#### h3_rads_to_degs(float) returns float + +Converts radians to degrees. + +``` +SELECT h3_rads_to_degs(degs_to_rads(90.45)); + h3_rads_to_degs +-------------- + 90.45 +(1 row) +``` + +#### h3_hex_area_km2(resolution integer) returns float + +Returns the area of an hex in square kilometers at the given resolution. + +``` +SELECT h3_hex_area_km2(10); + h3_hex_area_km2 +-------------- + 0.0150475 +(1 row) +``` + +#### h3_hex_area_m2(resolution integer) retuns float + +Returns the area of an hex in square meters at the given resolution. + +``` +SELECT h3_hex_area_m2(10); + h3_hex_area_m2 +------------- + 15047.5 +(1 row) +``` + +#### h3_edge_length_km(resolution integer) returns float + +Returns the length of the edges of an hex in kilometers at the given resolution. + +``` +SELECT h3_edge_length_km(10); + h3_edge_length_km +---------------- + 0.065907807 +(1 row) +``` + +#### h3_edge_length_m(resolution integer) returns float + +Returns the length of the edges of an hex in meters at the given resolution. + +``` +SELECT h3_edge_length_m(10); + h3_edge_length_m +--------------- + 65.90780749 +(1 row) +``` + +#### h3_num_hexagons(resolution integer) returns bigint + +Returns the number of unique indexes at the given resolution + +``` +SELECT h3_num_hexagons(15); + h3_num_hexagons +----------------- + 569707381193162 +(1 row) +``` + +### PostGIS integration + +We provide some simple wrappers for casting to PostGIS types. + +#### h3_geo_to_h3(geometry / geography, resolution) + +The `h3_geo_to_h3` function has been overloaded to support both PostGIS `geometry` and `geography`. + +#### h3_h3_to_geometry(h3index) returns geometry + +Finds the centroid of this hex as PostGIS geometry type. + +``` +SELECT h3_h3_to_geometry('8a63a9a99047fff'); + h3_h3_to_geometry +---------------------------------------------------- + 0101000020E61000008BE4AED877D54B40C46F27D42B2F2940 +(1 row) +``` + +#### h3_h3_to_geography(h3index) returns geography + +Same as above, but returns `geography` type. + +#### h3_h3_to_geo_boundary_geometry(h3index) returns geometry + +Find the boundary of this hex, as PostGIS type. + +``` +SELECT boundary_geometry('8a63a9a99047fff'); + boundary_geometry +------------------------- + 0103000020...FB70D54B40 +(1 row) +``` + +#### h3_h3_to_geo_boundary_geography(h3index) returns geography + +Same as above, but returns `geography` type. + +### Custom functions + +#### h3_basecells() returns setof h3index + +Returns all hexes at coarsest resolution. + +``` +SELECT h3_basecells(); + h3_basecells +----------------- + 8001fffffffffff + ... + 80f3fffffffffff +(122 rows) +``` + +#### haversine_distance(from h3index, to h3index) returns float + +Determines the distance between `from_hex` and `to_hex`, based on [Haversine Distance](https://en.wikipedia.org/wiki/Haversine_formula). + +``` +SELECT h3_haversine_distance('8f2830828052d25', '8f283082a30e623'); + h3_haversine_distance +-------------------- + 2.25685336707384 +(1 row) +``` diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..1b6a0590 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh +make install +make installcheck && make clean \ No newline at end of file diff --git a/h3.control b/h3.control new file mode 100644 index 00000000..1134cd91 --- /dev/null +++ b/h3.control @@ -0,0 +1,4 @@ +comment = 'H3 bindings for PostgreSQL' +default_version = '0.3.0' +relocatable = true +requires = 'postgis' diff --git a/sql/h3--0.0.0--0.1.0.sql b/sql/h3--0.0.0--0.1.0.sql new file mode 100644 index 00000000..d453eceb --- /dev/null +++ b/sql/h3--0.0.0--0.1.0.sql @@ -0,0 +1,115 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 FUNCTION h3index_in(cstring) RETURNS h3index + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION h3index_out(h3index) RETURNS cstring + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION h3index_eq(h3index, h3index) RETURNS boolean + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION h3index_ne(h3index, h3index) RETURNS boolean + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION h3index_lt(h3index, h3index) RETURNS boolean + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION h3index_le(h3index, h3index) RETURNS boolean + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION h3index_gt(h3index, h3index) RETURNS boolean + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION h3index_ge(h3index, h3index) RETURNS boolean + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE 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); diff --git a/sql/h3--0.1.0--0.2.0.sql b/sql/h3--0.1.0--0.2.0.sql new file mode 100644 index 00000000..3ced25eb --- /dev/null +++ b/sql/h3--0.1.0--0.2.0.sql @@ -0,0 +1,195 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 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 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 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 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 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 FUNCTION h3_string_to_h3(cstring) RETURNS h3index + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_string_to_h3(cstring) IS + 'Converts the string representation to H3Index representation'; +CREATE FUNCTION h3_h3_to_string(h3index) RETURNS cstring + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_h3_to_string(h3index) IS + 'Converts the H3Index representation of the index to the string representation'; +CREATE 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 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 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 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 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 FUNCTION h3_hex_range(h3index, k integer DEFAULT 1) RETURNS SETOF h3index + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_hex_range(h3index, k integer) IS + 'Produces indices within "k" distance of the origin index sorted by distance. Errors if a pentagon is encountered'; +CREATE FUNCTION h3_hex_range_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_hex_range_distances(h3index, k integer) IS + 'Produces indices within "k" distance of the origin index paired with their distance to the origin. + Sorted by increasing distance. Errors if a pentagon is encountered'; +CREATE FUNCTION h3_hex_ranges(h3index[], k integer DEFAULT 1) RETURNS SETOF h3index + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_hex_ranges(h3index[], k integer) IS + 'Returns the hex-range of the entire given array. Errors if a pentagon is encountered'; +CREATE 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 FUNCTION h3_degs_to_rads(float) RETURNS float + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_degs_to_rads(float) IS + 'Converts degrees to radians'; +CREATE FUNCTION h3_rads_to_degs(float) RETURNS float + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_rads_to_degs(float) IS + 'Converts radians to degrees.'; +CREATE FUNCTION h3_hex_area_km2(resolution integer) RETURNS float + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_hex_area_km2(resolution integer) IS + 'Average hexagon area in square kilometers at the given resolution.'; +CREATE FUNCTION h3_hex_area_m2(resolution integer) RETURNS float + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_hex_area_m2(resolution integer) IS + 'Average hexagon area in square meters at the given resolution.'; +CREATE FUNCTION h3_edge_length_km(resolution integer) RETURNS float + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_edge_length_km(resolution integer) IS + 'Average hexagon edge length in kilometers at the given resolution.'; +CREATE FUNCTION h3_edge_length_m(resolution integer) RETURNS float + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_edge_length_m(resolution integer) IS + 'Average hexagon edge length in meters at the given resolution.'; +CREATE 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.'; diff --git a/sql/h3--0.2.0--0.3.0.sql b/sql/h3--0.2.0--0.3.0.sql new file mode 100644 index 00000000..bcd9ad47 --- /dev/null +++ b/sql/h3--0.2.0--0.3.0.sql @@ -0,0 +1,98 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 FUNCTION h3_haversine_distance(h3index, h3index) RETURNS double precision + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + COMMENT ON FUNCTION h3_haversine_distance(h3index, h3index) IS + 'Returns the haversine distance between the two given indices.'; +CREATE 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 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 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 FUNCTION h3_geo_to_h3(geometry, resolution integer) RETURNS h3index + AS $$ SELECT h3_geo_to_h3($1::point, $2); $$ LANGUAGE SQL; +CREATE FUNCTION h3_geo_to_h3(geography, resolution integer) RETURNS h3index + AS $$ SELECT h3_geo_to_h3($1::geometry, $2); $$ LANGUAGE SQL; + +CREATE FUNCTION h3_h3_to_geometry(h3index) RETURNS geometry + AS $$ SELECT ST_SetSRID(h3_h3_to_geo($1)::geometry, 4326) $$ LANGUAGE SQL; +CREATE FUNCTION h3_h3_to_geography(h3index) RETURNS geography + AS $$ SELECT h3_h3_to_geometry($1)::geography $$ LANGUAGE SQL; + +CREATE 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 FUNCTION h3_h3_to_geo_boundary_geography(h3index) RETURNS geography + AS $$ SELECT h3_h3_to_geo_boundary_geometry($1)::geography $$ LANGUAGE SQL; + +CREATE 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 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); diff --git a/src/h3-pg.c b/src/h3-pg.c new file mode 100644 index 00000000..88d6970d --- /dev/null +++ b/src/h3-pg.c @@ -0,0 +1,170 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 // cos, sin, etc. + +#include // Datum, etc. +#include // PG_FUNCTION_ARGS, etc. +#include // Definitions for functions which return sets +#include // Needed to return HeapTuple + +#include

// Main H3 include +#include "h3-pg.h" + +// should only be in ONE file +PG_MODULE_MAGIC; + +/** + * 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 = PointerGetDatum(&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] = PointerGetDatum(&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); + } +} + +// Custom helper function that returns all basecells +PG_FUNCTION_INFO_V1(h3_basecells); +Datum h3_basecells(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + int i; + + if (SRF_IS_FIRSTCALL()) + { + SRF_FIRSTCALL_INIT(); + } + funcctx = SRF_PERCALL_SETUP(); + + i = funcctx->call_cntr; + if (i < 122) + { + Datum result; + H3Index *hex = palloc(sizeof(H3Index)); + + *hex = (UINT64_C(576495936675512319)); + *hex = (*hex & ~((uint64_t)(127) << 45)) | (((uint64_t)(i)) << 45); + + result = PointerGetDatum(hex); + SRF_RETURN_NEXT(funcctx, result); + } + else + { + SRF_RETURN_DONE(funcctx); + } +} + +/** + * @brief haversineDistance finds the + * [great-circle distance](https://en.wikipedia.org/wiki/Great-circle_distance) + * between two points on a sphere. + * @see https://en.wikipedia.org/wiki/Haversine_formula. + * + * Copied from H3 + */ + +// mean Earth radius +#define R 6371.0088 + +PG_FUNCTION_INFO_V1(h3_haversine_distance); +Datum h3_haversine_distance(PG_FUNCTION_ARGS) +{ + H3Index *h3HQ1 = PG_GETARG_H3_INDEX_P(0); + H3Index *h3HQ2 = PG_GETARG_H3_INDEX_P(1); + + double distance; + double dx, dy, dz, th1, ph1, th2, ph2; + + GeoCoord geoHQ1, geoHQ2; + h3ToGeo(*h3HQ1, &geoHQ1); + h3ToGeo(*h3HQ2, &geoHQ2); + + th1 = geoHQ1.lat; + ph1 = geoHQ1.lon; + th2 = geoHQ2.lat; + ph2 = geoHQ2.lon; + + ph1 -= ph2; + + dz = sin(th1) - sin(th2); + dx = cos(ph1) * cos(th1) - cos(th2); + dy = sin(ph1) * cos(th1); + distance = asin(sqrt(dx * dx + dy * dy + dz * dz) / 2) * 2 * R; + + PG_RETURN_FLOAT8(distance); +} diff --git a/src/h3-pg.h b/src/h3-pg.h new file mode 100644 index 00000000..7363c340 --- /dev/null +++ b/src/h3-pg.h @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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_H +#define PGH3_H + +#include

// Main H3 include + +typedef struct +{ + H3Index *indices; + int *distances; +} hexDistanceTuple; + +/* base type in postgres is Datum, and we cannot fit 8 bytes on all platforms + so we use pointers */ +#define PG_RETURN_H3_INDEX_P(x) PG_RETURN_POINTER(x) +#define PG_GETARG_H3_INDEX_P(x) (H3Index *)PG_GETARG_POINTER(x) + +Datum srf_return_h3_indexes_from_user_fctx(PG_FUNCTION_ARGS); +Datum srf_return_h3_index_distances_from_user_fctx(PG_FUNCTION_ARGS); + +#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) + +#define ENSURE_TYPEFUNC_COMPOSITE(x) \ + if (x != TYPEFUNC_COMPOSITE) \ + { \ + ereport(ERROR, \ + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ + errmsg("Function returning record called in context that cannot " \ + "accept type record"))); \ + } + +#endif diff --git a/src/hierarchy.c b/src/hierarchy.c new file mode 100644 index 00000000..99c9f2ca --- /dev/null +++ b/src/hierarchy.c @@ -0,0 +1,222 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 // Definitions for functions which return sets +#include // Arrays +#include // MaxAllocSize + +#include

// Main H3 include +#include "h3-pg.h" + +// Returns the parent (coarser) index containing given index +PG_FUNCTION_INFO_V1(h3_h3_to_parent); +Datum h3_h3_to_parent(PG_FUNCTION_ARGS) +{ + H3Index *parent; + + // get function arguments + H3Index *origin = PG_GETARG_H3_INDEX_P(0); + int resolution = PG_GETARG_INT32(1); + if (resolution == -1) + { // resolution parameter not set + resolution = h3GetResolution(*origin) - 1; + } + + // get parent + parent = palloc(sizeof(H3Index)); + *parent = h3ToParent(*origin, resolution); + if (!*parent) + { + ereport( + ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("Invalid resolution %d.", resolution), + errdetail( + "Current backend only has 16 resolutions numbered 0 through 15."), + errhint("Choose valid resolution."))); + } + + PG_RETURN_H3_INDEX_P(parent); +} + +// Returns children indexes at given resolution (or next resolution if none given) +PG_FUNCTION_INFO_V1(h3_h3_to_children); +Datum h3_h3_to_children(PG_FUNCTION_ARGS) +{ + // stuff done only on the first call of the function + if (SRF_IS_FIRSTCALL()) + { + int maxSize; + int 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_H3_INDEX_P(0); + int resolution = PG_GETARG_INT32(1); + if (resolution == -1) + { // resolution parameter not set + resolution = h3GetResolution(*origin) + 1; + } + + if (resolution > 15) + { + ereport( + ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("Maximum resolution exceeded."), + errdetail( + "Current backend only has 16 resolutions numbered 0 through 15."), + errhint("Reduce resolution."))); + } + + maxSize = maxH3ToChildrenSize(*origin, resolution); + size = maxSize * sizeof(H3Index); + + if (size > MaxAllocSize) + { + ereport( + ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("Cannot allocate requested memory. Try using h3_h3_to_children_slow()."))); + } + + children = palloc(size); + h3ToChildren(*origin, resolution, children); + + funcctx->user_fctx = children; + funcctx->max_calls = maxSize; + + // END One-time setup code + + MemoryContextSwitchTo(oldcontext); + } + + SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); +} + +PG_FUNCTION_INFO_V1(h3_compact); +Datum h3_compact(PG_FUNCTION_ARGS) +{ + if (SRF_IS_FIRSTCALL()) + { + FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); + MemoryContext oldcontext = + MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + + int arrayLength = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); + H3Index *h3set = palloc(sizeof(H3Index) * arrayLength); + H3Index *idx = (H3Index *)ARR_DATA_PTR(array); + + int maxSize = arrayLength; + H3Index *compactedSet = palloc0(maxSize * sizeof(H3Index)); + + // Extract data from array into h3set, and wipe compactedSet memory + for (int i = 0; i < arrayLength; i++) + { + h3set[i] = fetch_att(idx, true, sizeof(H3Index)); + idx++; + } + + if (compact(h3set, compactedSet, arrayLength) != 0) + { + elog(ERROR, "Something went wrong during compacting"); + } + + funcctx->user_fctx = compactedSet; + funcctx->max_calls = maxSize; + MemoryContextSwitchTo(oldcontext); + } + + SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); +} + +PG_FUNCTION_INFO_V1(h3_uncompact); +Datum h3_uncompact(PG_FUNCTION_ARGS) +{ + if (SRF_IS_FIRSTCALL()) + { + int maxSize; + H3Index *uncompactedSet; + + FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); + MemoryContext oldcontext = + MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + int resolution = PG_GETARG_INT32(1); + + int arrayLength = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); + H3Index *h3set = palloc(sizeof(H3Index) * arrayLength); + H3Index *idx = (H3Index *)ARR_DATA_PTR(array); + + // Extract data from array into h3set, and wipe compactedSet memory + for (int i = 0; i < arrayLength; i++) + { + h3set[i] = fetch_att(idx, true, sizeof(H3Index)); + idx++; + } + + if (resolution == -1) + { // resolution parameter not set + int highRes = 0; + // Find highest resolution in the given set + for (int i = 0; i < arrayLength; i++) + { + int curRes = h3GetResolution(h3set[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); + } + + maxSize = maxUncompactSize(h3set, arrayLength, resolution); + uncompactedSet = palloc0(maxSize * sizeof(H3Index)); + + if (uncompact(h3set, arrayLength, uncompactedSet, maxSize, resolution) != + 0) + { + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("Something went wrong during uncompacting"), + errdetail("This may be caused by choosing a lower resolution " + "than some of the indexes"), + errhint("Check that it is called with the proper resolution"))); + } + + funcctx->user_fctx = uncompactedSet; + funcctx->max_calls = maxSize; + MemoryContextSwitchTo(oldcontext); + } + + SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); +} \ No newline at end of file diff --git a/src/indexing.c b/src/indexing.c new file mode 100644 index 00000000..1ec1db70 --- /dev/null +++ b/src/indexing.c @@ -0,0 +1,90 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 // making native points + +#include

// Main H3 include +#include "h3-pg.h" + +// Indexes the location at the specified resolution +PG_FUNCTION_INFO_V1(h3_geo_to_h3); +Datum h3_geo_to_h3(PG_FUNCTION_ARGS) +{ + Point *geo = PG_GETARG_POINT_P(0); + int resolution = PG_GETARG_INT32(1); + + H3Index *idx = palloc(sizeof(H3Index)); + GeoCoord location; + location.lon = degsToRads(geo->x); + location.lat = degsToRads(geo->y); + + *idx = geoToH3(&location, resolution); + + if (*idx == 0) + { + ereport( + ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("Indexing failed"), + errdetail("GeoCoord could not be indexed at specified resolution."), + errhint("Reduce resolution."))); + } + + PG_RETURN_H3_INDEX_P(idx); +} + +// Finds the centroid of the index +PG_FUNCTION_INFO_V1(h3_h3_to_geo); +Datum h3_h3_to_geo(PG_FUNCTION_ARGS) +{ + H3Index *idx = PG_GETARG_H3_INDEX_P(0); + + Point *geo = palloc(sizeof(Point)); + GeoCoord center; + h3ToGeo(*idx, ¢er); + + geo->x = radsToDegs(center.lon); + geo->y = radsToDegs(center.lat); + + PG_RETURN_POINT_P(geo); +} + +// Finds the boundary of the index +PG_FUNCTION_INFO_V1(h3_h3_to_geo_boundary); +Datum h3_h3_to_geo_boundary(PG_FUNCTION_ARGS) +{ + H3Index *idx = PG_GETARG_H3_INDEX_P(0); + + int size; + POLYGON *polygon; + GeoBoundary boundary; + h3ToGeoBoundary(*idx, &boundary); + + size = offsetof(POLYGON, p) + 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].lon); + polygon->p[v].y = radsToDegs(boundary.verts[v].lat); + } + + PG_RETURN_POLYGON_P(polygon); +} \ No newline at end of file diff --git a/src/inspection.c b/src/inspection.c new file mode 100644 index 00000000..6ddb9f5a --- /dev/null +++ b/src/inspection.c @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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

// Main H3 include +#include "h3-pg.h" + +// Returns the resolution of the index +PG_FUNCTION_INFO_V1(h3_h3_get_resolution); +Datum h3_h3_get_resolution(PG_FUNCTION_ARGS) +{ + H3Index *hex = PG_GETARG_H3_INDEX_P(0); + int resolution = h3GetResolution(*hex); + PG_RETURN_INT32(resolution); +} + +// Returns the base cell number of the index +PG_FUNCTION_INFO_V1(h3_h3_get_base_cell); +Datum h3_h3_get_base_cell(PG_FUNCTION_ARGS) +{ + H3Index *hex = PG_GETARG_H3_INDEX_P(0); + int base_cell_number = h3GetBaseCell(*hex); + PG_RETURN_INT32(base_cell_number); +} + +// Converts the string representation to internal representation +PG_FUNCTION_INFO_V1(h3_string_to_h3); +Datum h3_string_to_h3(PG_FUNCTION_ARGS) +{ + H3Index *hex; + char *str; + + str = PG_GETARG_CSTRING(0); + hex = palloc(sizeof(H3Index)); + *hex = stringToH3(str); + + PG_RETURN_H3_INDEX_P(hex); +} + +// Converts the internal representation of the index to the string +// representation +PG_FUNCTION_INFO_V1(h3_h3_to_string); +Datum h3_h3_to_string(PG_FUNCTION_ARGS) +{ + H3Index *hex = PG_GETARG_H3_INDEX_P(0); + char *str = palloc(17 * sizeof(char)); + h3ToString(*hex, str, 17); + + PG_RETURN_CSTRING(str); +} + +// Returns true if this is a valid H3 index +PG_FUNCTION_INFO_V1(h3_h3_is_valid); +Datum h3_h3_is_valid(PG_FUNCTION_ARGS) +{ + H3Index *hex = PG_GETARG_H3_INDEX_P(0); + bool isValid = h3IsValid(*hex); + PG_RETURN_BOOL(isValid); +} + +// Returns true if this index has a resolution with Class III orientation +PG_FUNCTION_INFO_V1(h3_h3_is_res_class_iii); +Datum h3_h3_is_res_class_iii(PG_FUNCTION_ARGS) +{ + H3Index *hex = PG_GETARG_H3_INDEX_P(0); + bool isResClassIII = h3IsResClassIII(*hex); + PG_RETURN_BOOL(isResClassIII); +} + +// Returns true if this hex represents a pentagonal cell +PG_FUNCTION_INFO_V1(h3_h3_is_pentagon); +Datum h3_h3_is_pentagon(PG_FUNCTION_ARGS) +{ + H3Index *hex = PG_GETARG_H3_INDEX_P(0); + bool isPentagon = h3IsPentagon(*hex); + PG_RETURN_BOOL(isPentagon); +} diff --git a/src/miscellaneous.c b/src/miscellaneous.c new file mode 100644 index 00000000..1aeab80b --- /dev/null +++ b/src/miscellaneous.c @@ -0,0 +1,82 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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

// Main H3 include + +// Converts degrees to radians +PG_FUNCTION_INFO_V1(h3_degs_to_rads); +Datum h3_degs_to_rads(PG_FUNCTION_ARGS) +{ + double degrees = PG_GETARG_FLOAT8(0); + double radians = degsToRads(degrees); + PG_RETURN_FLOAT8(radians); +} + +// Converts radians to degrees +PG_FUNCTION_INFO_V1(h3_rads_to_degs); +Datum h3_rads_to_degs(PG_FUNCTION_ARGS) +{ + double radians = PG_GETARG_FLOAT8(0); + double degrees = radsToDegs(radians); + PG_RETURN_FLOAT8(degrees); +} + +// Average hexagon area in square kilometers at the given resolution +PG_FUNCTION_INFO_V1(h3_hex_area_km2); +Datum h3_hex_area_km2(PG_FUNCTION_ARGS) +{ + int resolution = PG_GETARG_INT32(0); + double area = hexAreaKm2(resolution); + PG_RETURN_FLOAT8(area); +} + +// Average hexagon area in square meters at the given resolution +PG_FUNCTION_INFO_V1(h3_hex_area_m2); +Datum h3_hex_area_m2(PG_FUNCTION_ARGS) +{ + int resolution = PG_GETARG_INT32(0); + double area = hexAreaM2(resolution); + PG_RETURN_FLOAT8(area); +} + +// Average hexagon edge length in kilometers at the given resolution +PG_FUNCTION_INFO_V1(h3_edge_length_km); +Datum h3_edge_length_km(PG_FUNCTION_ARGS) +{ + int resolution = PG_GETARG_INT32(0); + double length = edgeLengthKm(resolution); + PG_RETURN_FLOAT8(length); +} + +// Average hexagon edge length in meters at the given resolution +PG_FUNCTION_INFO_V1(h3_edge_length_m); +Datum h3_edge_length_m(PG_FUNCTION_ARGS) +{ + int resolution = PG_GETARG_INT32(0); + double length = edgeLengthM(resolution); + PG_RETURN_FLOAT8(length); +} + +// Number of unique H3 indexes at the given resolution +PG_FUNCTION_INFO_V1(h3_num_hexagons); +Datum h3_num_hexagons(PG_FUNCTION_ARGS) +{ + int resolution = PG_GETARG_INT32(0); + unsigned long long retVal = numHexagons(resolution); + PG_RETURN_INT64(retVal); +} \ No newline at end of file diff --git a/src/regions.c b/src/regions.c new file mode 100644 index 00000000..7485f496 --- /dev/null +++ b/src/regions.c @@ -0,0 +1,319 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 // Definitions for functions which return sets +#include // Needed to return HeapTuple +#include // using arrays +#include // making native points +#include +#include + +#include

// Main H3 include +#include "h3-pg.h" + +/** @struct GeoPolygon + * @brief Simplified core of GeoJSON Polygon coordinates definition + */ +/* +typedef struct { + double lat; ///< latitude in radians + double lon; ///< longitude in radians +} GeoCoord; + +typedef struct { + int numVerts; + GeoCoord *verts; +} Geofence; + +typedef struct { + Geofence geofence; ///< exterior boundary of the polygon + int numHoles; ///< number of elements in the array pointed to by holes + Geofence *holes; ///< interior boundaries (holes) in the polygon +} GeoPolygon; +*/ + +/** @struct LinkedGeoCoord + * @brief A coordinate node in a linked geo structure, part of a linked list + * +typedef struct LinkedGeoCoord LinkedGeoCoord; +struct LinkedGeoCoord +{ + GeoCoord vertex; + LinkedGeoCoord *next; +}; + +** @struct LinkedGeoLoop + * @brief A loop node in a linked geo structure, part of a linked list + * +typedef struct LinkedGeoLoop LinkedGeoLoop; +struct LinkedGeoLoop +{ + LinkedGeoCoord *first; + LinkedGeoCoord *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; +}; +*/ + +static void polygonToGeofence(POLYGON *polygon, Geofence *geofence) +{ + geofence->numVerts = polygon->npts; + geofence->verts = (GeoCoord *)palloc(geofence->numVerts * sizeof(GeoCoord)); + + for (int i = 0; i < geofence->numVerts; i++) + { + geofence->verts[i].lon = degsToRads(polygon->p[i].x); + geofence->verts[i].lat = degsToRads(polygon->p[i].y); + } +} + +static int linkedGeoLoopToNativePolygonSize(LinkedGeoLoop *linkedLoop) +{ + int count = 0; + LinkedGeoCoord *linkedCoord = linkedLoop->first; + while (linkedCoord != NULL) + { + count++; + linkedCoord = linkedCoord->next; + } + return count; +} + +static void linkedGeoLoopToNativePolygon(LinkedGeoLoop *linkedLoop, POLYGON *polygon) +{ + int count; + LinkedGeoCoord *linkedCoord = linkedLoop->first; + count = 0; + while (linkedCoord != NULL) + { + (polygon->p[count]).x = radsToDegs(linkedCoord->vertex.lon); + (polygon->p[count]).y = radsToDegs(linkedCoord->vertex.lat); + linkedCoord = linkedCoord->next; + count++; + } +} + +/** + * void polyfill(const GeoPolygon* geoPolygon, int res, H3Index* out); + */ +PG_FUNCTION_INFO_V1(h3_polyfill); +Datum h3_polyfill(PG_FUNCTION_ARGS) +{ + if (SRF_IS_FIRSTCALL()) + { + FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); + MemoryContext oldcontext = + MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + int maxSize; + H3Index *indices; + ArrayType *holes; + int *dim; + int ndim = 0; + int resolution; + GeoPolygon polygon; + + // get function arguments + POLYGON *exterior = PG_GETARG_POLYGON_P(0); + if (!PG_ARGISNULL(1)) + { + holes = PG_GETARG_ARRAYTYPE_P(1); + dim = ARR_DIMS(holes); + ndim = ARR_NDIM(holes); + } + resolution = PG_GETARG_INT32(2); + + // build polygon + polygonToGeofence(exterior, &(polygon.geofence)); + + if (ndim == 1) + { + POLYGON *hole = (POLYGON *)ARR_DATA_PTR(holes); + + // build holes + polygon.numHoles = dim[0]; + polygon.holes = (Geofence *)palloc(polygon.numHoles * sizeof(Geofence)); + + for (int i = 0; i < polygon.numHoles; i++) + { + polygonToGeofence(hole, &(polygon.holes[i])); + hole++; + } + } + else + { + polygon.numHoles = 0; + } + + // produce hexagons into allocated memory + maxSize = maxPolyfillSize(&polygon, resolution); + + // LOG_DEBUG("MAXSIZE %i", maxSize * sizeof(H3Index)); + + indices = palloc0(maxSize * sizeof(H3Index)); + polyfill(&polygon, resolution, indices); + + funcctx->user_fctx = indices; + funcctx->max_calls = maxSize; + MemoryContextSwitchTo(oldcontext); + } + + SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); +} + +/** + * void polyfill(const GeoPolygon* geoPolygon, int res, H3Index* out); + * + * + * https://stackoverflow.com/questions/51127189/how-to-return-array-into-array-with-custom-type-in-postgres-c-function + */ +PG_FUNCTION_INFO_V1(h3_h3_set_to_linked_geo); +Datum h3_h3_set_to_linked_geo(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tuple_desc; + + LinkedGeoPolygon *linkedPolygon; + LinkedGeoLoop *linkedLoop; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + ArrayType *array; + int numHexes; + H3Index *h3Set; + H3Index *idx; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + // get function arguments + array = PG_GETARG_ARRAYTYPE_P(0); + + numHexes = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); + h3Set = palloc(sizeof(H3Index) * numHexes); + idx = (H3Index *)ARR_DATA_PTR(array); + + for (int i = 0; i < numHexes; i++) + { + h3Set[i] = fetch_att(idx, true, sizeof(H3Index)); + idx++; + } + + // produce hexagons into allocated memory + linkedPolygon = palloc(sizeof(LinkedGeoPolygon)); + h3SetToLinkedGeo(h3Set, numHexes, linkedPolygon); + + 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 + { + destroyLinkedPolygon(linkedPolygon); + SRF_RETURN_DONE(funcctx); + } +} diff --git a/src/traversal.c b/src/traversal.c new file mode 100644 index 00000000..1d7818fc --- /dev/null +++ b/src/traversal.c @@ -0,0 +1,390 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 // Definitions for functions which return sets +#include // Needed to return HeapTuple +#include // Arrays +#include // making native points + +#include

// Main H3 include +#include "h3-pg.h" + +/** + * 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. + */ +PG_FUNCTION_INFO_V1(h3_k_ring); +Datum h3_k_ring(PG_FUNCTION_ARGS) +{ + if (SRF_IS_FIRSTCALL()) + { + FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); + MemoryContext oldcontext = + MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + // get function arguments + H3Index *origin = PG_GETARG_H3_INDEX_P(0); + int k = PG_GETARG_INT32(1); + + // produce indices into allocated memory + int maxSize = maxKringSize(k); + H3Index *indices = palloc(maxSize * sizeof(H3Index)); + kRing(*origin, k, indices); + + funcctx->user_fctx = indices; + funcctx->max_calls = maxSize; + 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. + */ +PG_FUNCTION_INFO_V1(h3_k_ring_distances); +Datum h3_k_ring_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_H3_INDEX_P(0); + int k = PG_GETARG_INT32(1); + + // Allocate memory for the indices, the distances and the tuple used for + // returning + int maxSize = maxKringSize(k); + hexDistanceTuple *user_fctx = palloc(sizeof(hexDistanceTuple)); + user_fctx->indices = palloc(maxSize * sizeof(H3Index)); + user_fctx->distances = palloc(maxSize * sizeof(int)); + + kRingDistances(*origin, k, user_fctx->indices, user_fctx->distances); + + if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Function returning record called in context that cannot " + "accept type record"))); + } + + 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(); +} + +/** + * hexRange produces indexes within k distance of the origin index. Output + * behavior is undefined when one of the indexes returned by this function is a + * pentagon or is in the pentagon distortion area. + * + * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and + * all neighboring indexes, and so on. + * + * Output is placed in the provided array in order of increasing distance from + * the origin. + * + * Throws if pentagonal distortion is encountered. + */ +PG_FUNCTION_INFO_V1(h3_hex_range); +Datum h3_hex_range(PG_FUNCTION_ARGS) +{ + if (SRF_IS_FIRSTCALL()) + { + FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); + MemoryContext oldcontext = + MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + // get function arguments + H3Index *origin = PG_GETARG_H3_INDEX_P(0); + int k = PG_GETARG_INT32(1); + + // produce indices into allocated memory + int maxSize = maxKringSize(k); + H3Index *indices = palloc(maxSize * sizeof(H3Index)); + + if (hexRange(*origin, k, indices) != 0) + { + ereport( + ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Pentagon encountered"), + errdetail("This method is undefined when it encounters pentagons"), + errhint("Try using k_ring"))); + } + + funcctx->user_fctx = indices; + funcctx->max_calls = maxSize; + MemoryContextSwitchTo(oldcontext); + } + + SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); +} + +/** + * hexRangeDistances produces indexes within k distance of the origin index. + * Output behavior is undefined when one of the indexes returned by this + * function is a pentagon or is in the pentagon distortion area. + * + * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and + * all neighboring indexes, and so on. + * + * Output is placed in the provided array in order of increasing distance from + * the origin. The distances in hexagons is placed in the distances array at the + * same offset. + * + * Throws if pentagonal distortion is encountered. + * + */ +PG_FUNCTION_INFO_V1(h3_hex_range_distances); +Datum h3_hex_range_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_H3_INDEX_P(0); + int k = PG_GETARG_INT32(1); + + // Allocate memory for the indices, the distances and the tuple used for + // returning + int maxSize = maxKringSize(k); + H3Index *indices = palloc(maxSize * sizeof(H3Index)); + int *distances = palloc(maxSize * sizeof(int)); + hexDistanceTuple *user_fctx = palloc(sizeof(hexDistanceTuple)); + + if (hexRangeDistances(*origin, k, indices, distances) != 0) + { + ereport( + ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Pentagon encountered"), + errdetail("This method is undefined when it encounters pentagons"), + errhint("Try using k_ring_distances"))); + } + + if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Function returning record called in context that cannot " + "accept type record"))); + } + + user_fctx->indices = indices; + user_fctx->distances = distances; + 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(); +} + +/** + * hexRanges takes an array of input hex IDs and a max k-ring and returns an + * array of hexagon IDs sorted first by the original hex IDs and then by the + * k-ring (0 to max), with no guaranteed sorting within each k-ring group. + * + * Throws if pentagonal distortion was encountered. + */ +PG_FUNCTION_INFO_V1(h3_hex_ranges); +Datum h3_hex_ranges(PG_FUNCTION_ARGS) +{ + if (SRF_IS_FIRSTCALL()) + { + FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); + MemoryContext oldcontext = + MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + // get function arguments + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + int k = PG_GETARG_INT32(1); + + int length = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); + H3Index *h3Set = palloc(sizeof(H3Index) * length); + H3Index *idx = (H3Index *)ARR_DATA_PTR(array); + + int maxSize = maxKringSize(k) * length; + H3Index *indices = palloc(maxSize * sizeof(H3Index)); + + for (int i = 0; i < length; i++) + { + h3Set[i] = fetch_att(idx, true, sizeof(H3Index)); + idx++; + } + + if (hexRanges(h3Set, length, k, indices) != 0) + { + ereport( + ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Pentagon encountered"), + errdetail("This method is undefined when it encounters pentagons"), + errhint("Try using k_ring"))); + } + + funcctx->user_fctx = indices; + funcctx->max_calls = maxSize; + MemoryContextSwitchTo(oldcontext); + } + + SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); +} + +/** + * Produces the hollow hexagonal ring centered at origin with sides of length k. + * + * Throws if pentagonal distortion was encountered. + */ +PG_FUNCTION_INFO_V1(h3_hex_ring); +Datum h3_hex_ring(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_H3_INDEX_P(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 + int maxSize = maxKringSize(k); + if (k > 0) + { + maxSize -= maxKringSize(k - 1); + } + indices = palloc(maxSize * sizeof(H3Index)); + + if (hexRing(*origin, k, indices) != 0) + { + ereport( + ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Pentagon encountered"), + errdetail("This method is undefined when it encounters pentagons"))); + } + + 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. + */ +PG_FUNCTION_INFO_V1(h3_distance); +Datum h3_distance(PG_FUNCTION_ARGS) +{ + H3Index *originIndex = PG_GETARG_H3_INDEX_P(0); + H3Index *h3Index = PG_GETARG_H3_INDEX_P(1); + int distance; + + distance = h3Distance(*originIndex, *h3Index); + + PG_RETURN_INT32(distance); +} + +/** + * 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. + */ +PG_FUNCTION_INFO_V1(h3_experimental_h3_to_local_ij); +Datum h3_experimental_h3_to_local_ij(PG_FUNCTION_ARGS) +{ + H3Index *origin = PG_GETARG_H3_INDEX_P(0); + H3Index *index = PG_GETARG_H3_INDEX_P(1); + + Point *point = (Point *)palloc(sizeof(Point)); + CoordIJ coord; + experimentalH3ToLocalIj(*origin, *index, &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. + * + * This function is experimental, and its output is not guaranteed to be + * compatible across different versions of H3. + */ +PG_FUNCTION_INFO_V1(h3_experimental_local_ij_to_h3); +Datum h3_experimental_local_ij_to_h3(PG_FUNCTION_ARGS) +{ + H3Index *origin = PG_GETARG_H3_INDEX_P(0); + Point *point = PG_GETARG_POINT_P(1); + + H3Index *index = (H3Index *)palloc(sizeof(H3Index)); + + CoordIJ coord; + coord.i = point->x; + coord.j = point->y; + + experimentalLocalIjToH3(*origin, &coord, index); + + PG_RETURN_H3_INDEX_P(index); +} diff --git a/src/type.c b/src/type.c new file mode 100644 index 00000000..700a79a7 --- /dev/null +++ b/src/type.c @@ -0,0 +1,106 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 // making native points + +#include

// Main H3 include +#include "h3-pg.h" + +Datum h3_string_to_h3(PG_FUNCTION_ARGS); +Datum h3_h3_to_string(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(h3index_in); +PG_FUNCTION_INFO_V1(h3index_out); +PG_FUNCTION_INFO_V1(h3index_eq); +PG_FUNCTION_INFO_V1(h3index_ne); +PG_FUNCTION_INFO_V1(h3index_lt); +PG_FUNCTION_INFO_V1(h3index_le); +PG_FUNCTION_INFO_V1(h3index_gt); +PG_FUNCTION_INFO_V1(h3index_ge); +PG_FUNCTION_INFO_V1(h3index_cmp); + +Datum h3index_in(PG_FUNCTION_ARGS) +{ + return h3_string_to_h3(fcinfo); +} + +Datum h3index_out(PG_FUNCTION_ARGS) +{ + return h3_h3_to_string(fcinfo); +} + +Datum h3index_eq(PG_FUNCTION_ARGS) +{ + H3Index *a = PG_GETARG_H3_INDEX_P(0); + H3Index *b = PG_GETARG_H3_INDEX_P(1); + PG_RETURN_BOOL(*a == *b); +} + +Datum h3index_ne(PG_FUNCTION_ARGS) +{ + H3Index *a = PG_GETARG_H3_INDEX_P(0); + H3Index *b = PG_GETARG_H3_INDEX_P(1); + PG_RETURN_BOOL(*a != *b); +} + +Datum h3index_lt(PG_FUNCTION_ARGS) +{ + H3Index *a = PG_GETARG_H3_INDEX_P(0); + H3Index *b = PG_GETARG_H3_INDEX_P(1); + PG_RETURN_BOOL(*a < *b); +} + +Datum h3index_le(PG_FUNCTION_ARGS) +{ + H3Index *a = PG_GETARG_H3_INDEX_P(0); + H3Index *b = PG_GETARG_H3_INDEX_P(1); + PG_RETURN_BOOL(*a <= *b); +} + +Datum h3index_gt(PG_FUNCTION_ARGS) +{ + H3Index *a = PG_GETARG_H3_INDEX_P(0); + H3Index *b = PG_GETARG_H3_INDEX_P(1); + PG_RETURN_BOOL(*a > *b); +} + +Datum h3index_ge(PG_FUNCTION_ARGS) +{ + H3Index *a = PG_GETARG_H3_INDEX_P(0); + H3Index *b = PG_GETARG_H3_INDEX_P(1); + PG_RETURN_BOOL(*a >= *b); +} + +Datum h3index_cmp(PG_FUNCTION_ARGS) +{ + H3Index *a = PG_GETARG_H3_INDEX_P(0); + H3Index *b = PG_GETARG_H3_INDEX_P(1); + + if (*a > *b) + { + PG_RETURN_INT32(1); + } + else if (a == b) + { + PG_RETURN_INT32(0); + } + else + { + PG_RETURN_INT32(-1); + } +} \ No newline at end of file diff --git a/src/uniedges.c b/src/uniedges.c new file mode 100644 index 00000000..3c698278 --- /dev/null +++ b/src/uniedges.c @@ -0,0 +1,159 @@ +/* + * Copyright 2018 Bytes & Brains + * + * 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 // Needed to return HeapTuple +#include //Needed to return HeapTuple +#include // making native points + +#include

// Main H3 include +#include "h3-pg.h" + +PG_FUNCTION_INFO_V1(h3_h3_indexes_are_neighbors); +PG_FUNCTION_INFO_V1(h3_get_h3_unidirectional_edge); +PG_FUNCTION_INFO_V1(h3_h3_unidirectional_edge_is_valid); +PG_FUNCTION_INFO_V1(h3_get_origin_h3_index_from_unidirectional_edge); +PG_FUNCTION_INFO_V1(h3_get_destination_h3_index_from_unidirectional_edge); +PG_FUNCTION_INFO_V1(h3_get_h3_indexes_from_unidirectional_edge); +PG_FUNCTION_INFO_V1(h3_get_h3_unidirectional_edges_from_hexagon); +PG_FUNCTION_INFO_V1(h3_get_unidirectional_edge_boundary); + +/* Returns whether or not the provided H3Indexes are neighbors */ +Datum h3_h3_indexes_are_neighbors(PG_FUNCTION_ARGS) +{ + H3Index *origin = PG_GETARG_H3_INDEX_P(0); + H3Index *destination = PG_GETARG_H3_INDEX_P(1); + bool areNeighbors = h3IndexesAreNeighbors(*origin, *destination); + PG_RETURN_BOOL(areNeighbors); +} + +/* + * Returns a unidirectional edge H3 index based on the provided origin and + * destination + */ +Datum h3_get_h3_unidirectional_edge(PG_FUNCTION_ARGS) +{ + H3Index *origin = PG_GETARG_H3_INDEX_P(0); + H3Index *destination = PG_GETARG_H3_INDEX_P(1); + H3Index *edge = palloc(sizeof(H3Index)); + *edge = getH3UnidirectionalEdge(*origin, *destination); + if (*edge == 0) + { + ereport(ERROR, ( + errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("Can only create edges between neighbouring indexes"))); + } + PG_RETURN_H3_INDEX_P(edge); +} + +/* Determines if the provided H3Index is a valid unidirectional edge index */ +Datum h3_h3_unidirectional_edge_is_valid(PG_FUNCTION_ARGS) +{ + H3Index *edge = PG_GETARG_H3_INDEX_P(0); + bool isValid = h3UnidirectionalEdgeIsValid(*edge); + PG_RETURN_BOOL(isValid); +} + +/* Returns the origin hexagon from the unidirectional edge H3Index */ +Datum h3_get_origin_h3_index_from_unidirectional_edge(PG_FUNCTION_ARGS) +{ + H3Index *edge = PG_GETARG_H3_INDEX_P(0); + H3Index *origin = palloc(sizeof(H3Index)); + *origin = getOriginH3IndexFromUnidirectionalEdge(*edge); + PG_RETURN_H3_INDEX_P(origin); +} + +/* Returns the destination hexagon from the unidirectional edge H3Index */ +Datum h3_get_destination_h3_index_from_unidirectional_edge(PG_FUNCTION_ARGS) +{ + H3Index *edge = PG_GETARG_H3_INDEX_P(0); + H3Index *destination = palloc(sizeof(H3Index)); + *destination = getDestinationH3IndexFromUnidirectionalEdge(*edge); + PG_RETURN_H3_INDEX_P(destination); +} + +/* Returns the origin, destination pair of hexagon IDs for the given edge ID */ +Datum h3_get_h3_indexes_from_unidirectional_edge(PG_FUNCTION_ARGS) +{ + TupleDesc tuple_desc; + Datum values[2]; + bool nulls[2] = {false}; + HeapTuple tuple; + Datum result; + + H3Index *edge = PG_GETARG_H3_INDEX_P(0); + H3Index *indexes = palloc(sizeof(H3Index) * 2); + getH3IndexesFromUnidirectionalEdge(*edge, indexes); + + ENSURE_TYPEFUNC_COMPOSITE(get_call_result_type(fcinfo, NULL, &tuple_desc)); + tuple_desc = BlessTupleDesc(tuple_desc); + + values[0] = PointerGetDatum(indexes); + indexes++; // increment pointer to next index + values[1] = PointerGetDatum(indexes); + + 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_get_h3_unidirectional_edges_from_hexagon(PG_FUNCTION_ARGS) +{ + if (SRF_IS_FIRSTCALL()) + { + FuncCallContext *funcctx = SRF_FIRSTCALL_INIT(); + MemoryContext oldcontext = + MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + H3Index *origin = PG_GETARG_H3_INDEX_P(0); + int maxSize = 6; + H3Index *edges = palloc(sizeof(H3Index) * maxSize); + getH3UnidirectionalEdgesFromHexagon(*origin, edges); + + funcctx->user_fctx = edges; + funcctx->max_calls = maxSize; + MemoryContextSwitchTo(oldcontext); + } + + SRF_RETURN_H3_INDEXES_FROM_USER_FCTX(); +} + +/* Provides the coordinates defining the unidirectional edge */ +Datum h3_get_unidirectional_edge_boundary(PG_FUNCTION_ARGS) +{ + H3Index *edge = PG_GETARG_H3_INDEX_P(0); + + GeoBoundary geoBoundary; + POLYGON *polygon; + int size; + + getH3UnidirectionalEdgeBoundary(*edge, &geoBoundary); + + size = offsetof(POLYGON, p[0]) + sizeof(polygon->p[0]) * geoBoundary.numVerts; + polygon = (POLYGON *)palloc(size); + SET_VARSIZE(polygon, size); + polygon->npts = geoBoundary.numVerts; + + for (int v = 0; v < geoBoundary.numVerts; v++) + { + polygon->p[v].x = radsToDegs(geoBoundary.verts[v].lat); + polygon->p[v].y = radsToDegs(geoBoundary.verts[v].lon); + } + + PG_RETURN_POLYGON_P(polygon); +} \ No newline at end of file diff --git a/test/expected/h3-pg.out b/test/expected/h3-pg.out new file mode 100644 index 00000000..08f487b6 --- /dev/null +++ b/test/expected/h3-pg.out @@ -0,0 +1,133 @@ +SELECT h3_haversine_distance('8f2830828052d25', '8f283082a30e623'); + h3_haversine_distance +----------------------- + 2.25685336707537 +(1 row) + +SELECT h3_basecells(); + h3_basecells +----------------- + 8001fffffffffff + 8003fffffffffff + 8005fffffffffff + 8007fffffffffff + 8009fffffffffff + 800bfffffffffff + 800dfffffffffff + 800ffffffffffff + 8011fffffffffff + 8013fffffffffff + 8015fffffffffff + 8017fffffffffff + 8019fffffffffff + 801bfffffffffff + 801dfffffffffff + 801ffffffffffff + 8021fffffffffff + 8023fffffffffff + 8025fffffffffff + 8027fffffffffff + 8029fffffffffff + 802bfffffffffff + 802dfffffffffff + 802ffffffffffff + 8031fffffffffff + 8033fffffffffff + 8035fffffffffff + 8037fffffffffff + 8039fffffffffff + 803bfffffffffff + 803dfffffffffff + 803ffffffffffff + 8041fffffffffff + 8043fffffffffff + 8045fffffffffff + 8047fffffffffff + 8049fffffffffff + 804bfffffffffff + 804dfffffffffff + 804ffffffffffff + 8051fffffffffff + 8053fffffffffff + 8055fffffffffff + 8057fffffffffff + 8059fffffffffff + 805bfffffffffff + 805dfffffffffff + 805ffffffffffff + 8061fffffffffff + 8063fffffffffff + 8065fffffffffff + 8067fffffffffff + 8069fffffffffff + 806bfffffffffff + 806dfffffffffff + 806ffffffffffff + 8071fffffffffff + 8073fffffffffff + 8075fffffffffff + 8077fffffffffff + 8079fffffffffff + 807bfffffffffff + 807dfffffffffff + 807ffffffffffff + 8081fffffffffff + 8083fffffffffff + 8085fffffffffff + 8087fffffffffff + 8089fffffffffff + 808bfffffffffff + 808dfffffffffff + 808ffffffffffff + 8091fffffffffff + 8093fffffffffff + 8095fffffffffff + 8097fffffffffff + 8099fffffffffff + 809bfffffffffff + 809dfffffffffff + 809ffffffffffff + 80a1fffffffffff + 80a3fffffffffff + 80a5fffffffffff + 80a7fffffffffff + 80a9fffffffffff + 80abfffffffffff + 80adfffffffffff + 80affffffffffff + 80b1fffffffffff + 80b3fffffffffff + 80b5fffffffffff + 80b7fffffffffff + 80b9fffffffffff + 80bbfffffffffff + 80bdfffffffffff + 80bffffffffffff + 80c1fffffffffff + 80c3fffffffffff + 80c5fffffffffff + 80c7fffffffffff + 80c9fffffffffff + 80cbfffffffffff + 80cdfffffffffff + 80cffffffffffff + 80d1fffffffffff + 80d3fffffffffff + 80d5fffffffffff + 80d7fffffffffff + 80d9fffffffffff + 80dbfffffffffff + 80ddfffffffffff + 80dffffffffffff + 80e1fffffffffff + 80e3fffffffffff + 80e5fffffffffff + 80e7fffffffffff + 80e9fffffffffff + 80ebfffffffffff + 80edfffffffffff + 80effffffffffff + 80f1fffffffffff + 80f3fffffffffff +(122 rows) + diff --git a/test/expected/hierarchy.out b/test/expected/hierarchy.out new file mode 100644 index 00000000..f6774bc3 --- /dev/null +++ b/test/expected/hierarchy.out @@ -0,0 +1,402 @@ +\set hexagon '\'880326b88dfffff\'' +\set pentagon '\'831c00fffffffff\'' +SELECT array_length(array_agg(i), 1), 7 expected FROM ( + SELECT h3_h3_to_children(:hexagon) i +) q; + array_length | expected +--------------+---------- + 7 | 7 +(1 row) + +SELECT array_length(array_agg(i), 1), 6 expected FROM ( + SELECT h3_h3_to_children(:pentagon) i +) q; + array_length | expected +--------------+---------- + 6 | 6 +(1 row) + +SELECT h3_h3_to_children(:pentagon); + h3_h3_to_children +------------------- + 841c001ffffffff + 841c005ffffffff + 841c007ffffffff + 841c009ffffffff + 841c00bffffffff + 841c00dffffffff +(6 rows) + +SELECT h3_h3_to_children(h3_h3_to_parent(:hexagon)); + h3_h3_to_children +------------------- + 880326b881fffff + 880326b883fffff + 880326b885fffff + 880326b887fffff + 880326b889fffff + 880326b88bfffff + 880326b88dfffff +(7 rows) + +SELECT h3_h3_to_parent(h3_h3_to_children(:hexagon)); + h3_h3_to_parent +----------------- + 880326b88dfffff + 880326b88dfffff + 880326b88dfffff + 880326b88dfffff + 880326b88dfffff + 880326b88dfffff + 880326b88dfffff +(7 rows) + +SELECT h3_h3_get_resolution(:hexagon); + h3_h3_get_resolution +---------------------- + 8 +(1 row) + +SELECT h3_h3_get_resolution(h3_h3_to_parent(:hexagon)); + h3_h3_get_resolution +---------------------- + 7 +(1 row) + +SELECT h3_h3_get_resolution(h3_h3_to_children(:hexagon)); + h3_h3_get_resolution +---------------------- + 9 + 9 + 9 + 9 + 9 + 9 + 9 +(7 rows) + +SELECT h3_h3_to_children(h3_h3_to_parent(:pentagon)); + h3_h3_to_children +------------------- + 831c00fffffffff + 831c02fffffffff + 831c03fffffffff + 831c04fffffffff + 831c05fffffffff + 831c06fffffffff +(6 rows) + +SELECT h3_h3_to_parent(h3_h3_to_children(:pentagon)); + h3_h3_to_parent +----------------- + 831c00fffffffff + 831c00fffffffff + 831c00fffffffff + 831c00fffffffff + 831c00fffffffff + 831c00fffffffff +(6 rows) + +SELECT h3_h3_get_resolution(:pentagon); + h3_h3_get_resolution +---------------------- + 3 +(1 row) + +SELECT h3_h3_get_resolution(h3_h3_to_parent(:pentagon)); + h3_h3_get_resolution +---------------------- + 2 +(1 row) + +SELECT h3_h3_get_resolution(h3_h3_to_children(:pentagon)); + h3_h3_get_resolution +---------------------- + 4 + 4 + 4 + 4 + 4 + 4 +(6 rows) + +SELECT h3_compact(array_cat(ARRAY(SELECT h3_h3_to_children('880326b88dfffff')), ARRAY(SELECT h3_h3_to_children('880326b88bfffff')))); + h3_compact +----------------- + 880326b88bfffff + 880326b88dfffff +(2 rows) + +--Checks that the uncompact call finds the sum of the two below calls. i.e. uncompacts all to same resolution +SELECT h3_uncompact(array_cat(ARRAY(SELECT h3_h3_to_children('880326b88dfffff')), '{880326b88bfffff}'::h3index[])); + h3_uncompact +----------------- + 8a0326b88c07fff + 8a0326b88c0ffff + 8a0326b88c17fff + 8a0326b88c1ffff + 8a0326b88c27fff + 8a0326b88c2ffff + 8a0326b88c37fff + 8a0326b88c47fff + 8a0326b88c4ffff + 8a0326b88c57fff + 8a0326b88c5ffff + 8a0326b88c67fff + 8a0326b88c6ffff + 8a0326b88c77fff + 8a0326b88c87fff + 8a0326b88c8ffff + 8a0326b88c97fff + 8a0326b88c9ffff + 8a0326b88ca7fff + 8a0326b88caffff + 8a0326b88cb7fff + 8a0326b88cc7fff + 8a0326b88ccffff + 8a0326b88cd7fff + 8a0326b88cdffff + 8a0326b88ce7fff + 8a0326b88ceffff + 8a0326b88cf7fff + 8a0326b88d07fff + 8a0326b88d0ffff + 8a0326b88d17fff + 8a0326b88d1ffff + 8a0326b88d27fff + 8a0326b88d2ffff + 8a0326b88d37fff + 8a0326b88d47fff + 8a0326b88d4ffff + 8a0326b88d57fff + 8a0326b88d5ffff + 8a0326b88d67fff + 8a0326b88d6ffff + 8a0326b88d77fff + 8a0326b88d87fff + 8a0326b88d8ffff + 8a0326b88d97fff + 8a0326b88d9ffff + 8a0326b88da7fff + 8a0326b88daffff + 8a0326b88db7fff + 8a0326b88a07fff + 8a0326b88a0ffff + 8a0326b88a17fff + 8a0326b88a1ffff + 8a0326b88a27fff + 8a0326b88a2ffff + 8a0326b88a37fff + 8a0326b88a47fff + 8a0326b88a4ffff + 8a0326b88a57fff + 8a0326b88a5ffff + 8a0326b88a67fff + 8a0326b88a6ffff + 8a0326b88a77fff + 8a0326b88a87fff + 8a0326b88a8ffff + 8a0326b88a97fff + 8a0326b88a9ffff + 8a0326b88aa7fff + 8a0326b88aaffff + 8a0326b88ab7fff + 8a0326b88ac7fff + 8a0326b88acffff + 8a0326b88ad7fff + 8a0326b88adffff + 8a0326b88ae7fff + 8a0326b88aeffff + 8a0326b88af7fff + 8a0326b88b07fff + 8a0326b88b0ffff + 8a0326b88b17fff + 8a0326b88b1ffff + 8a0326b88b27fff + 8a0326b88b2ffff + 8a0326b88b37fff + 8a0326b88b47fff + 8a0326b88b4ffff + 8a0326b88b57fff + 8a0326b88b5ffff + 8a0326b88b67fff + 8a0326b88b6ffff + 8a0326b88b77fff + 8a0326b88b87fff + 8a0326b88b8ffff + 8a0326b88b97fff + 8a0326b88b9ffff + 8a0326b88ba7fff + 8a0326b88baffff + 8a0326b88bb7fff +(98 rows) + +SELECT h3_h3_to_children(h3_h3_to_children('880326b88bfffff')); + h3_h3_to_children +------------------- + 8a0326b88a07fff + 8a0326b88a0ffff + 8a0326b88a17fff + 8a0326b88a1ffff + 8a0326b88a27fff + 8a0326b88a2ffff + 8a0326b88a37fff + 8a0326b88a47fff + 8a0326b88a4ffff + 8a0326b88a57fff + 8a0326b88a5ffff + 8a0326b88a67fff + 8a0326b88a6ffff + 8a0326b88a77fff + 8a0326b88a87fff + 8a0326b88a8ffff + 8a0326b88a97fff + 8a0326b88a9ffff + 8a0326b88aa7fff + 8a0326b88aaffff + 8a0326b88ab7fff + 8a0326b88ac7fff + 8a0326b88acffff + 8a0326b88ad7fff + 8a0326b88adffff + 8a0326b88ae7fff + 8a0326b88aeffff + 8a0326b88af7fff + 8a0326b88b07fff + 8a0326b88b0ffff + 8a0326b88b17fff + 8a0326b88b1ffff + 8a0326b88b27fff + 8a0326b88b2ffff + 8a0326b88b37fff + 8a0326b88b47fff + 8a0326b88b4ffff + 8a0326b88b57fff + 8a0326b88b5ffff + 8a0326b88b67fff + 8a0326b88b6ffff + 8a0326b88b77fff + 8a0326b88b87fff + 8a0326b88b8ffff + 8a0326b88b97fff + 8a0326b88b9ffff + 8a0326b88ba7fff + 8a0326b88baffff + 8a0326b88bb7fff +(49 rows) + +SELECT h3_h3_to_children(h3_h3_to_children('880326b88dfffff')); + h3_h3_to_children +------------------- + 8a0326b88c07fff + 8a0326b88c0ffff + 8a0326b88c17fff + 8a0326b88c1ffff + 8a0326b88c27fff + 8a0326b88c2ffff + 8a0326b88c37fff + 8a0326b88c47fff + 8a0326b88c4ffff + 8a0326b88c57fff + 8a0326b88c5ffff + 8a0326b88c67fff + 8a0326b88c6ffff + 8a0326b88c77fff + 8a0326b88c87fff + 8a0326b88c8ffff + 8a0326b88c97fff + 8a0326b88c9ffff + 8a0326b88ca7fff + 8a0326b88caffff + 8a0326b88cb7fff + 8a0326b88cc7fff + 8a0326b88ccffff + 8a0326b88cd7fff + 8a0326b88cdffff + 8a0326b88ce7fff + 8a0326b88ceffff + 8a0326b88cf7fff + 8a0326b88d07fff + 8a0326b88d0ffff + 8a0326b88d17fff + 8a0326b88d1ffff + 8a0326b88d27fff + 8a0326b88d2ffff + 8a0326b88d37fff + 8a0326b88d47fff + 8a0326b88d4ffff + 8a0326b88d57fff + 8a0326b88d5ffff + 8a0326b88d67fff + 8a0326b88d6ffff + 8a0326b88d77fff + 8a0326b88d87fff + 8a0326b88d8ffff + 8a0326b88d97fff + 8a0326b88d9ffff + 8a0326b88da7fff + 8a0326b88daffff + 8a0326b88db7fff +(49 rows) + +SELECT h3_uncompact(array_cat(ARRAY(SELECT h3_h3_to_parent('880326b88dfffff')), '{880326b88bfffff}'::h3index[])); + h3_uncompact +----------------- + 890326b8803ffff + 890326b8807ffff + 890326b880bffff + 890326b880fffff + 890326b8813ffff + 890326b8817ffff + 890326b881bffff + 890326b8823ffff + 890326b8827ffff + 890326b882bffff + 890326b882fffff + 890326b8833ffff + 890326b8837ffff + 890326b883bffff + 890326b8843ffff + 890326b8847ffff + 890326b884bffff + 890326b884fffff + 890326b8853ffff + 890326b8857ffff + 890326b885bffff + 890326b8863ffff + 890326b8867ffff + 890326b886bffff + 890326b886fffff + 890326b8873ffff + 890326b8877ffff + 890326b887bffff + 890326b8883ffff + 890326b8887ffff + 890326b888bffff + 890326b888fffff + 890326b8893ffff + 890326b8897ffff + 890326b889bffff + 890326b88a3ffff + 890326b88a7ffff + 890326b88abffff + 890326b88afffff + 890326b88b3ffff + 890326b88b7ffff + 890326b88bbffff + 890326b88c3ffff + 890326b88c7ffff + 890326b88cbffff + 890326b88cfffff + 890326b88d3ffff + 890326b88d7ffff + 890326b88dbffff + 890326b88a3ffff + 890326b88a7ffff + 890326b88abffff + 890326b88afffff + 890326b88b3ffff + 890326b88b7ffff + 890326b88bbffff +(56 rows) + diff --git a/test/expected/indexing.out b/test/expected/indexing.out new file mode 100644 index 00000000..307e495e --- /dev/null +++ b/test/expected/indexing.out @@ -0,0 +1,60 @@ +\set geometry POINT('64.7498111652365,89.5695822308866') +\set resolution 8 +\set hexagon '\'880326b88dfffff\'' +\set pentagon '\'844c001ffffffff\'' +\set pentagon_edgecross '\'831c00fffffffff\'' +-- Index/coord conversions +SELECT h3_h3_to_geo(idx), h3_h3_get_resolution(idx) FROM ( + SELECT h3_geo_to_h3(:geometry, :resolution) AS idx +) AS q; + h3_h3_to_geo | h3_h3_get_resolution +-------------------------------------+---------------------- + (64.7498111652365,89.5695822308866) | 8 +(1 row) + +SELECT h3_geo_to_h3(geo, res) FROM ( + SELECT h3_h3_to_geo(:hexagon) AS geo, h3_h3_get_resolution(:hexagon) AS res +) AS q; + h3_geo_to_h3 +----------------- + 880326b88dfffff +(1 row) + +SELECT h3_geo_to_h3(geo, res) FROM ( + SELECT h3_h3_to_geo(:pentagon) AS geo, h3_h3_get_resolution(:pentagon) AS res +) AS q; + h3_geo_to_h3 +----------------- + 844c001ffffffff +(1 row) + +SELECT h3_h3_to_geo_boundary(:hexagon); + h3_h3_to_geo_boundary +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ((64.3352882950961,89.5656359347422),(64.9735428791841,89.5648291130886),(65.3931051992816,89.5687402592777),(65.1719930581934,89.5735056770753),(64.5210916348838,89.5743285361039),(64.104106930976,89.570369702947)) +(1 row) + +SELECT h3_h3_to_geo_boundary(:pentagon); + h3_h3_to_geo_boundary +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ((-67.0431837139629,23.8723443291486),(-67.2653769611427,23.843121188364),(-67.3034129124381,23.6407971449002),(-67.1052392995192,23.5450603765006),(-66.9444189451127,23.6880109937544)) +(1 row) + +SELECT h3_h3_to_geo_boundary(:pentagon_edgecross); + h3_h3_to_geo_boundary +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ((-143.6016553151,50.5338271422596),(-144.031945825263,50.3066616230969),(-144.155631118115,50.1598692121925),(-143.949729085173,49.8293441809871),(-143.77201562177,49.70837721268),(-143.222289746467,49.7294855170065),(-142.988158352874,49.7997443658674),(-142.844448066145,50.1435818584537),(-142.874989673059,50.3091889524779),(-143.344037347522,50.5025794565712)) +(1 row) + +SELECT h3_h3_to_geo('880326b88dfffff'); + h3_h3_to_geo +------------------------------------- + (64.7498111652365,89.5695822308866) +(1 row) + +SELECT h3_geo_to_h3(POINT('64.7498111652365,89.5695822308866'), 8); + h3_geo_to_h3 +----------------- + 880326b88dfffff +(1 row) + diff --git a/test/expected/inspection.out b/test/expected/inspection.out new file mode 100644 index 00000000..407d44d6 --- /dev/null +++ b/test/expected/inspection.out @@ -0,0 +1,45 @@ +\set invalid '\'0\'' +\set hexagon '\'880326b88dfffff\'' +\set pentagon '\'831c00fffffffff\'' +SELECT h3_h3_is_pentagon(:hexagon), h3_h3_is_pentagon(:pentagon); + h3_h3_is_pentagon | h3_h3_is_pentagon +-------------------+------------------- + f | t +(1 row) + +SELECT h3_h3_is_valid(:hexagon), h3_h3_is_valid(:pentagon), h3_h3_is_valid(:invalid); + h3_h3_is_valid | h3_h3_is_valid | h3_h3_is_valid +----------------+----------------+---------------- + t | t | f +(1 row) + +SELECT h3_h3_get_resolution(:hexagon), h3_h3_get_resolution(:pentagon); + h3_h3_get_resolution | h3_h3_get_resolution +----------------------+---------------------- + 8 | 3 +(1 row) + +SELECT h3_h3_get_base_cell(:hexagon), h3_h3_get_base_cell(h3_h3_to_parent(:hexagon)); + h3_h3_get_base_cell | h3_h3_get_base_cell +---------------------+--------------------- + 1 | 1 +(1 row) + +SELECT h3_h3_get_base_cell(:pentagon), h3_h3_get_base_cell(h3_h3_to_parent(:pentagon)); + h3_h3_get_base_cell | h3_h3_get_base_cell +---------------------+--------------------- + 14 | 14 +(1 row) + +SELECT h3_h3_is_res_class_iii(:hexagon), h3_h3_is_res_class_iii(h3_h3_to_parent(:hexagon)); + h3_h3_is_res_class_iii | h3_h3_is_res_class_iii +------------------------+------------------------ + f | t +(1 row) + +SELECT h3_h3_is_res_class_iii(:pentagon), h3_h3_is_res_class_iii(h3_h3_to_parent(:pentagon)); + h3_h3_is_res_class_iii | h3_h3_is_res_class_iii +------------------------+------------------------ + t | f +(1 row) + diff --git a/test/expected/miscellaneous.out b/test/expected/miscellaneous.out new file mode 100644 index 00000000..35299935 --- /dev/null +++ b/test/expected/miscellaneous.out @@ -0,0 +1,42 @@ +SELECT h3_rads_to_degs(h3_degs_to_rads(90.45)); + h3_rads_to_degs +----------------- + 90.45 +(1 row) + +SELECT h3_hex_area_km2(10); + h3_hex_area_km2 +----------------- + 0.0150475 +(1 row) + +SELECT h3_hex_area_m2(10); + h3_hex_area_m2 +---------------- + 15047.5 +(1 row) + +SELECT h3_edge_length_km(10); + h3_edge_length_km +------------------- + 0.065907807 +(1 row) + +SELECT h3_edge_length_m(10); + h3_edge_length_m +------------------ + 65.90780749 +(1 row) + +SELECT h3_num_hexagons(0); + h3_num_hexagons +----------------- + 122 +(1 row) + +SELECT h3_num_hexagons(15); + h3_num_hexagons +----------------- + 569707381193162 +(1 row) + diff --git a/test/expected/postgis.out b/test/expected/postgis.out new file mode 100644 index 00000000..3008dc62 --- /dev/null +++ b/test/expected/postgis.out @@ -0,0 +1,21 @@ +-- Variables for testing +\set resolution 10 +\set hexagon '\'8a63a9a99047fff\'' +SELECT h3_geo_to_h3(h3_h3_to_geo(:hexagon), :resolution); + h3_geo_to_h3 +----------------- + 8a63a9a99047fff +(1 row) + +SELECT h3_h3_to_geo_boundary(:hexagon); + h3_h3_to_geo_boundary +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ((55.6675104208598,12.5926363927131),(55.6671647139638,12.5921138130542),(55.6673742176306,12.5916086849498),(55.6679294210098,12.5916261357095),(55.6682751250888,12.5921487089117),(55.6680656286056,12.5926538378107)) +(1 row) + +SELECT ST_AsEWKT( h3_h3_to_geo_boundary_geometry(:hexagon)); + st_asewkt +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + SRID=4326;POLYGON((55.6675104208598 12.5926363927131,55.6671647139638 12.5921138130542,55.6673742176306 12.5916086849498,55.6679294210098 12.5916261357095,55.6682751250888 12.5921487089117,55.6680656286056 12.5926538378107,55.6675104208598 12.5926363927131)) +(1 row) + diff --git a/test/expected/regions.out b/test/expected/regions.out new file mode 100644 index 00000000..ce7bf7a6 --- /dev/null +++ b/test/expected/regions.out @@ -0,0 +1,66 @@ +-- basecell around middle +\set idx_basecell '\'8059fffffffffff\'' +-- 7 child hexes in basecell +\set idz_7hex 'ARRAY(SELECT h3_h3_to_children(:idx_basecell, 1))' +-- 6 child hexes in outer rim of basecell +\set idz_6hex 'ARRAY(SELECT id FROM (SELECT h3_h3_to_children(:idx_basecell, 1) id) q WHERE id != \'81583ffffffffff\')' +SELECT h3_h3_get_resolution(:idx_basecell); + h3_h3_get_resolution +---------------------- + 0 +(1 row) + +-- BACK AND FORTH +SELECT :idz_7hex; + array +------------------------------------------------------------------------------------------------------------------- + {81583ffffffffff,81587ffffffffff,8158bffffffffff,8158fffffffffff,81593ffffffffff,81597ffffffffff,8159bffffffffff} +(1 row) + +SELECT (h3_h3_set_to_linked_geo(:idz_7hex)).exterior, (h3_h3_set_to_linked_geo(:idz_7hex)).holes; + exterior | holes +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------- + ((13.9151838856346,23.4504377153203),(9.38067660520161,25.6037025769688),(5.36757370512317,23.0111470503629),(0.994677192549833,24.8583358344496),(-2.48386501194795,22.1975413863024),(-1.71130990397791,17.9354045818174),(-3.29256482968365,16.7189804058919),(-4.86687202658957,15.2198852193985),(-4.01399844347047,11.5452959754148),(-0.319182726856614,9.73183667097592),(0.309803963535533,5.86451836551763),(3.94303615578646,3.96879697660959),(7.21291456272007,5.86039216837659),(9.10237141854828,4.86081181207326),(10.7227778272856,3.78962499175013),(14.5832500714117,5.76286049143693),(14.4350416743716,9.91339077600533),(18.371312883447,12.0234431609871),(18.3651793828161,16.5059476035611),(14.1025321744588,18.8007437662402)) | {} +(1 row) + +SELECT h3_polyfill(exterior, holes, 1) FROM +( + SELECT * FROM h3_h3_set_to_linked_geo(:idz_7hex) +) q; + h3_polyfill +----------------- + 8158fffffffffff + 8159bffffffffff + 8158bffffffffff + 81597ffffffffff + 81587ffffffffff + 81593ffffffffff + 81583ffffffffff +(7 rows) + +SELECT :idz_6hex; + array +--------------------------------------------------------------------------------------------------- + {81587ffffffffff,8158bffffffffff,8158fffffffffff,81593ffffffffff,81597ffffffffff,8159bffffffffff} +(1 row) + +SELECT (h3_h3_set_to_linked_geo(:idz_6hex)).exterior, (h3_h3_set_to_linked_geo(:idz_6hex)).holes; + exterior | holes +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + ((18.371312883447,12.0234431609871),(18.3651793828161,16.5059476035611),(14.1025321744588,18.8007437662402),(13.9151838856346,23.4504377153203),(9.38067660520161,25.6037025769688),(5.36757370512317,23.0111470503629),(0.994677192549833,24.8583358344496),(-2.48386501194795,22.1975413863024),(-1.71130990397791,17.9354045818174),(-3.29256482968365,16.7189804058919),(-4.86687202658957,15.2198852193985),(-4.01399844347047,11.5452959754148),(-0.319182726856614,9.73183667097592),(0.309803963535533,5.86451836551763),(3.94303615578646,3.96879697660959),(7.21291456272007,5.86039216837659),(9.10237141854828,4.86081181207326),(10.7227778272856,3.78962499175013),(14.5832500714117,5.76286049143693),(14.4350416743716,9.91339077600533)) | {"((2.30284280920586,16.0986886429595),(5.88226299580354,18.5160524000961),(10.0979776613705,16.4491832881973),(10.4162881227623,12.0573252262095),(6.80116931700978,9.89974307082342),(2.88810760150022,11.881770859939))"} +(1 row) + +SELECT h3_polyfill(exterior, holes, 1) FROM +( + SELECT * FROM h3_h3_set_to_linked_geo(:idz_6hex) +) q; + h3_polyfill +----------------- + 8158fffffffffff + 8159bffffffffff + 8158bffffffffff + 81597ffffffffff + 81587ffffffffff + 81593ffffffffff +(6 rows) + diff --git a/test/expected/traversal.out b/test/expected/traversal.out new file mode 100644 index 00000000..d6fe91b7 --- /dev/null +++ b/test/expected/traversal.out @@ -0,0 +1,417 @@ +\set hexagon '\'880326b88dfffff\'' +\set origin '\'880326b887fffff\'' +\set pentagon '\'831c00fffffffff\'' +SELECT h3_k_ring(:hexagon); + h3_k_ring +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff +(7 rows) + +SELECT h3_k_ring(h3_k_ring(:hexagon)); + h3_k_ring +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff + 880326b8ebfffff + 880326b8e9fffff + 880326b8e1fffff + 880326b8e3fffff + 880326b88dfffff + 880326b8c7fffff + 880326b8c5fffff + 880326b8e3fffff + 880326b8e1fffff + 880326b8e7fffff + 880326b8a9fffff + 880326b885fffff + 880326b88dfffff + 880326b8ebfffff + 880326b885fffff + 880326b8e3fffff + 880326b8a9fffff + 880326b8abfffff + 880326b887fffff + 880326b881fffff + 880326b88dfffff + 880326b881fffff + 880326b88dfffff + 880326b885fffff + 880326b887fffff + 880326b883fffff + 880326b88bfffff + 880326b889fffff + 880326b889fffff + 880326b8c7fffff + 880326b88dfffff + 880326b881fffff + 880326b88bfffff + 880326b8d5fffff + 880326b8c3fffff + 880326b8c7fffff + 880326b8c5fffff + 880326b8ebfffff + 880326b88dfffff + 880326b889fffff + 880326b8c3fffff + 880326b8c1fffff +(49 rows) + +SELECT * FROM h3_k_ring_distances(:hexagon); + index | distance +-----------------+---------- + 880326b88dfffff | 0 + 880326b8ebfffff | 1 + 880326b8e3fffff | 1 + 880326b885fffff | 1 + 880326b881fffff | 1 + 880326b889fffff | 1 + 880326b8c7fffff | 1 +(7 rows) + +SELECT h3_k_ring_distances(:pentagon); + h3_k_ring_distances +--------------------- + (831c04fffffffff,1) + (831c05fffffffff,1) + (831c06fffffffff,1) + (831c00fffffffff,0) + (831c02fffffffff,1) + (831c03fffffffff,1) +(6 rows) + +SELECT h3_k_ring(:hexagon); + h3_k_ring +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff +(7 rows) + +SELECT h3_hex_range(:hexagon); + h3_hex_range +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff +(7 rows) + +SELECT h3_k_ring(:hexagon, 2); + h3_k_ring +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff + 880326b8c5fffff + 880326b8e9fffff + 880326b8e1fffff + 880326b8e7fffff + 880326b8a9fffff + 880326b8abfffff + 880326b887fffff + 880326b883fffff + 880326b88bfffff + 880326b8d5fffff + 880326b8c3fffff + 880326b8c1fffff +(19 rows) + +SELECT h3_hex_range(:hexagon, 2); + h3_hex_range +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff + 880326b8c5fffff + 880326b8e9fffff + 880326b8e1fffff + 880326b8e7fffff + 880326b8a9fffff + 880326b8abfffff + 880326b887fffff + 880326b883fffff + 880326b88bfffff + 880326b8d5fffff + 880326b8c3fffff + 880326b8c1fffff +(19 rows) + +SELECT * FROM h3_hex_range_distances(:hexagon, 2); + index | distance +-----------------+---------- + 880326b88dfffff | 0 + 880326b8ebfffff | 1 + 880326b8e3fffff | 1 + 880326b885fffff | 1 + 880326b881fffff | 1 + 880326b889fffff | 1 + 880326b8c7fffff | 1 + 880326b8c5fffff | 2 + 880326b8e9fffff | 2 + 880326b8e1fffff | 2 + 880326b8e7fffff | 2 + 880326b8a9fffff | 2 + 880326b8abfffff | 2 + 880326b887fffff | 2 + 880326b883fffff | 2 + 880326b88bfffff | 2 + 880326b8d5fffff | 2 + 880326b8c3fffff | 2 + 880326b8c1fffff | 2 +(19 rows) + +SELECT h3_hex_range(:hexagon); + h3_hex_range +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff +(7 rows) + +SELECT h3_hex_range('880326b8ebfffff'); + h3_hex_range +----------------- + 880326b8ebfffff + 880326b8e9fffff + 880326b8e1fffff + 880326b8e3fffff + 880326b88dfffff + 880326b8c7fffff + 880326b8c5fffff +(7 rows) + +SELECT h3_hex_ranges('{880326b88dfffff,880326b8ebfffff}'::h3index[]); + h3_hex_ranges +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff + 880326b8ebfffff + 880326b8e9fffff + 880326b8e1fffff + 880326b8e3fffff + 880326b88dfffff + 880326b8c7fffff + 880326b8c5fffff +(14 rows) + +SELECT h3_hex_range(:hexagon, 2); + h3_hex_range +----------------- + 880326b88dfffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff + 880326b8c7fffff + 880326b8c5fffff + 880326b8e9fffff + 880326b8e1fffff + 880326b8e7fffff + 880326b8a9fffff + 880326b8abfffff + 880326b887fffff + 880326b883fffff + 880326b88bfffff + 880326b8d5fffff + 880326b8c3fffff + 880326b8c1fffff +(19 rows) + +SELECT h3_hex_ring(:hexagon, 1); + h3_hex_ring +----------------- + 880326b8c7fffff + 880326b8ebfffff + 880326b8e3fffff + 880326b885fffff + 880326b881fffff + 880326b889fffff +(6 rows) + +SELECT h3_hex_ring(:hexagon, 2); + h3_hex_ring +----------------- + 880326b8c1fffff + 880326b8c5fffff + 880326b8e9fffff + 880326b8e1fffff + 880326b8e7fffff + 880326b8a9fffff + 880326b8abfffff + 880326b887fffff + 880326b883fffff + 880326b88bfffff + 880326b8d5fffff + 880326b8c3fffff +(12 rows) + +SELECT h3_h3_get_resolution(:hexagon); + h3_h3_get_resolution +---------------------- + 8 +(1 row) + +SELECT h3_h3_get_resolution(h3_k_ring(:hexagon)); + h3_h3_get_resolution +---------------------- + 8 + 8 + 8 + 8 + 8 + 8 + 8 +(7 rows) + +SELECT h3_h3_get_resolution(h3_hex_range(:hexagon)); + h3_h3_get_resolution +---------------------- + 8 + 8 + 8 + 8 + 8 + 8 + 8 +(7 rows) + +SELECT h3_k_ring(:pentagon); + h3_k_ring +----------------- + 831c04fffffffff + 831c05fffffffff + 831c06fffffffff + 831c00fffffffff + 831c02fffffffff + 831c03fffffffff +(6 rows) + +SELECT h3_k_ring(h3_k_ring(:pentagon)); + h3_k_ring +----------------- + 831c04fffffffff + 831c05fffffffff + 831c06fffffffff + 831c00fffffffff + 831c23fffffffff + 831c22fffffffff + 831c31fffffffff + 831c2efffffffff + 831c05fffffffff + 831c04fffffffff + 831c00fffffffff + 831c2afffffffff + 831c23fffffffff + 831c03fffffffff + 831c04fffffffff + 831c33fffffffff + 831c06fffffffff + 831c15fffffffff + 831c00fffffffff + 831c02fffffffff + 831c31fffffffff + 831c04fffffffff + 831c05fffffffff + 831c06fffffffff + 831c00fffffffff + 831c02fffffffff + 831c03fffffffff + 831c03fffffffff + 831c15fffffffff + 831c06fffffffff + 831c1cfffffffff + 831c00fffffffff + 831c02fffffffff + 831c11fffffffff + 831c00fffffffff + 831c05fffffffff + 831c02fffffffff + 831c1cfffffffff + 831c1dfffffffff + 831c2afffffffff + 831c03fffffffff +(41 rows) + +SELECT h3_h3_get_resolution(:pentagon); + h3_h3_get_resolution +---------------------- + 3 +(1 row) + +SELECT h3_h3_get_resolution(h3_k_ring(:pentagon)); + h3_h3_get_resolution +---------------------- + 3 + 3 + 3 + 3 + 3 + 3 +(6 rows) + +SELECT h3_h3_indexes_are_neighbors(:hexagon, '880326b8ebfffff'), h3_h3_indexes_are_neighbors('880326b881fffff', '880326b8ebfffff'); + h3_h3_indexes_are_neighbors | h3_h3_indexes_are_neighbors +-----------------------------+----------------------------- + t | f +(1 row) + +SELECT h3_distance('880326b881fffff', '880326b885fffff'); + h3_distance +------------- + 1 +(1 row) + +SELECT h3_distance('880326b881fffff', h3_h3_to_parent('880326b885fffff')); + h3_distance +------------- + -1 +(1 row) + +SELECT h3_experimental_h3_to_local_ij(:origin, :hexagon); + h3_experimental_h3_to_local_ij +-------------------------------- + (1478,456) +(1 row) + +SELECT :hexagon, h3_experimental_local_ij_to_h3(:origin, h3_experimental_h3_to_local_ij(:origin, :hexagon)); + ?column? | h3_experimental_local_ij_to_h3 +-----------------+-------------------------------- + 880326b88dfffff | 880326b88dfffff +(1 row) + diff --git a/test/expected/type.out b/test/expected/type.out new file mode 100644 index 00000000..cf953f56 --- /dev/null +++ b/test/expected/type.out @@ -0,0 +1,42 @@ +\set geometry POINT('64.7498111652365,89.5695822308866') +\set resolution 8 +\set hexagon '\'880326b88dfffff\'' +\set pentagon '\'844c001ffffffff\'' +\set pentagon_edgecross '\'831c00fffffffff\'' +-- Type conversions +SELECT h3_string_to_h3(h3_h3_to_string(:hexagon)); + h3_string_to_h3 +----------------- + 880326b88dfffff +(1 row) + +SELECT h3_h3_to_string(h3_string_to_h3(:hexagon)); + h3_h3_to_string +----------------- + 880326b88dfffff +(1 row) + +SELECT h3_string_to_h3(:hexagon) = h3_string_to_h3(:hexagon); + ?column? +---------- + t +(1 row) + +SELECT h3_string_to_h3(:hexagon) = h3_string_to_h3(:pentagon); + ?column? +---------- + f +(1 row) + +SELECT h3_string_to_h3(:hexagon) <> h3_string_to_h3(:hexagon); + ?column? +---------- + f +(1 row) + +SELECT h3_string_to_h3(:hexagon) <> h3_string_to_h3(:pentagon); + ?column? +---------- + t +(1 row) + diff --git a/test/expected/uniedges.out b/test/expected/uniedges.out new file mode 100644 index 00000000..76a6a939 --- /dev/null +++ b/test/expected/uniedges.out @@ -0,0 +1,92 @@ +\set hexagon '\'880326b885fffff\'' +\set neighbor '\'880326b887fffff\'' +\set pentagon '\'831c00fffffffff\'' +\set uniedge 'h3_get_h3_unidirectional_edge(:hexagon, :neighbor)' +SELECT :uniedge; + h3_get_h3_unidirectional_edge +------------------------------- + 1180326b885fffff +(1 row) + +SELECT h3_h3_indexes_are_neighbors(:hexagon, :neighbor), TRUE expected; + h3_h3_indexes_are_neighbors | expected +-----------------------------+---------- + t | t +(1 row) + +SELECT h3_h3_indexes_are_neighbors(:hexagon, :hexagon), FALSE expected; + h3_h3_indexes_are_neighbors | expected +-----------------------------+---------- + f | f +(1 row) + +SELECT h3_h3_unidirectional_edge_is_valid(:uniedge), TRUE expected; + h3_h3_unidirectional_edge_is_valid | expected +------------------------------------+---------- + t | t +(1 row) + +SELECT h3_h3_unidirectional_edge_is_valid(:hexagon), FALSE expected; + h3_h3_unidirectional_edge_is_valid | expected +------------------------------------+---------- + f | f +(1 row) + +SELECT + h3_get_origin_h3_index_from_unidirectional_edge(:uniedge), + :hexagon expected; + h3_get_origin_h3_index_from_unidirectional_edge | expected +-------------------------------------------------+----------------- + 880326b885fffff | 880326b885fffff +(1 row) + +SELECT + h3_get_destination_h3_index_from_unidirectional_edge(:uniedge), + :neighbor expected; + h3_get_destination_h3_index_from_unidirectional_edge | expected +------------------------------------------------------+----------------- + 880326b887fffff | 880326b887fffff +(1 row) + +SELECT + h3_get_h3_indexes_from_unidirectional_edge(:uniedge), + (:hexagon, :neighbor) expected; + h3_get_h3_indexes_from_unidirectional_edge | expected +--------------------------------------------+----------------------------------- + (880326b885fffff,880326b887fffff) | (880326b885fffff,880326b887fffff) +(1 row) + +SELECT array_length(array_agg(edge), 1), 6 expected FROM ( + SELECT h3_get_h3_unidirectional_edges_from_hexagon(:hexagon) edge +) q; + array_length | expected +--------------+---------- + 6 | 6 +(1 row) + +SELECT array_length(array_agg(edge), 1), 5 expected FROM ( + SELECT h3_get_h3_unidirectional_edges_from_hexagon(:pentagon) edge +) q; + array_length | expected +--------------+---------- + 5 | 5 +(1 row) + +SELECT h3_get_origin_h3_index_from_unidirectional_edge(h3_get_h3_unidirectional_edges_from_hexagon(:hexagon)), + h3_get_destination_h3_index_from_unidirectional_edge(h3_get_h3_unidirectional_edges_from_hexagon(:hexagon)); + h3_get_origin_h3_index_from_unidirectional_edge | h3_get_destination_h3_index_from_unidirectional_edge +-------------------------------------------------+------------------------------------------------------ + 880326b885fffff | 880326b887fffff + 880326b885fffff | 880326b8a9fffff + 880326b885fffff | 880326b8abfffff + 880326b885fffff | 880326b88dfffff + 880326b885fffff | 880326b881fffff + 880326b885fffff | 880326b8e3fffff +(6 rows) + +SELECT h3_get_unidirectional_edge_boundary(:uniedge); + h3_get_unidirectional_edge_boundary +--------------------------------------------------------------------------- + ((89.5830164946548,64.7146398954916),(89.5790678021742,64.2872231517217)) +(1 row) + diff --git a/test/sql/h3-pg.sql b/test/sql/h3-pg.sql new file mode 100644 index 00000000..11319f4b --- /dev/null +++ b/test/sql/h3-pg.sql @@ -0,0 +1,2 @@ +SELECT h3_haversine_distance('8f2830828052d25', '8f283082a30e623'); +SELECT h3_basecells(); \ No newline at end of file diff --git a/test/sql/hierarchy.sql b/test/sql/hierarchy.sql new file mode 100644 index 00000000..526e0ba6 --- /dev/null +++ b/test/sql/hierarchy.sql @@ -0,0 +1,34 @@ +\set hexagon '\'880326b88dfffff\'' +\set pentagon '\'831c00fffffffff\'' + +SELECT array_length(array_agg(i), 1), 7 expected FROM ( + SELECT h3_h3_to_children(:hexagon) i +) q; + +SELECT array_length(array_agg(i), 1), 6 expected FROM ( + SELECT h3_h3_to_children(:pentagon) i +) q; + +SELECT h3_h3_to_children(:pentagon); + +SELECT h3_h3_to_children(h3_h3_to_parent(:hexagon)); +SELECT h3_h3_to_parent(h3_h3_to_children(:hexagon)); + +SELECT h3_h3_get_resolution(:hexagon); +SELECT h3_h3_get_resolution(h3_h3_to_parent(:hexagon)); +SELECT h3_h3_get_resolution(h3_h3_to_children(:hexagon)); + +SELECT h3_h3_to_children(h3_h3_to_parent(:pentagon)); +SELECT h3_h3_to_parent(h3_h3_to_children(:pentagon)); + +SELECT h3_h3_get_resolution(:pentagon); +SELECT h3_h3_get_resolution(h3_h3_to_parent(:pentagon)); +SELECT h3_h3_get_resolution(h3_h3_to_children(:pentagon)); + +SELECT h3_compact(array_cat(ARRAY(SELECT h3_h3_to_children('880326b88dfffff')), ARRAY(SELECT h3_h3_to_children('880326b88bfffff')))); +--Checks that the uncompact call finds the sum of the two below calls. i.e. uncompacts all to same resolution +SELECT h3_uncompact(array_cat(ARRAY(SELECT h3_h3_to_children('880326b88dfffff')), '{880326b88bfffff}'::h3index[])); +SELECT h3_h3_to_children(h3_h3_to_children('880326b88bfffff')); +SELECT h3_h3_to_children(h3_h3_to_children('880326b88dfffff')); + +SELECT h3_uncompact(array_cat(ARRAY(SELECT h3_h3_to_parent('880326b88dfffff')), '{880326b88bfffff}'::h3index[])); \ No newline at end of file diff --git a/test/sql/indexing.sql b/test/sql/indexing.sql new file mode 100644 index 00000000..fce69995 --- /dev/null +++ b/test/sql/indexing.sql @@ -0,0 +1,23 @@ +\set geometry POINT('64.7498111652365,89.5695822308866') +\set resolution 8 +\set hexagon '\'880326b88dfffff\'' +\set pentagon '\'844c001ffffffff\'' +\set pentagon_edgecross '\'831c00fffffffff\'' + +-- Index/coord conversions +SELECT h3_h3_to_geo(idx), h3_h3_get_resolution(idx) FROM ( + SELECT h3_geo_to_h3(:geometry, :resolution) AS idx +) AS q; +SELECT h3_geo_to_h3(geo, res) FROM ( + SELECT h3_h3_to_geo(:hexagon) AS geo, h3_h3_get_resolution(:hexagon) AS res +) AS q; +SELECT h3_geo_to_h3(geo, res) FROM ( + SELECT h3_h3_to_geo(:pentagon) AS geo, h3_h3_get_resolution(:pentagon) AS res +) AS q; + +SELECT h3_h3_to_geo_boundary(:hexagon); +SELECT h3_h3_to_geo_boundary(:pentagon); +SELECT h3_h3_to_geo_boundary(:pentagon_edgecross); + +SELECT h3_h3_to_geo('880326b88dfffff'); +SELECT h3_geo_to_h3(POINT('64.7498111652365,89.5695822308866'), 8); diff --git a/test/sql/inspection.sql b/test/sql/inspection.sql new file mode 100644 index 00000000..2680ba20 --- /dev/null +++ b/test/sql/inspection.sql @@ -0,0 +1,15 @@ +\set invalid '\'0\'' +\set hexagon '\'880326b88dfffff\'' +\set pentagon '\'831c00fffffffff\'' + +SELECT h3_h3_is_pentagon(:hexagon), h3_h3_is_pentagon(:pentagon); + +SELECT h3_h3_is_valid(:hexagon), h3_h3_is_valid(:pentagon), h3_h3_is_valid(:invalid); + +SELECT h3_h3_get_resolution(:hexagon), h3_h3_get_resolution(:pentagon); + +SELECT h3_h3_get_base_cell(:hexagon), h3_h3_get_base_cell(h3_h3_to_parent(:hexagon)); +SELECT h3_h3_get_base_cell(:pentagon), h3_h3_get_base_cell(h3_h3_to_parent(:pentagon)); + +SELECT h3_h3_is_res_class_iii(:hexagon), h3_h3_is_res_class_iii(h3_h3_to_parent(:hexagon)); +SELECT h3_h3_is_res_class_iii(:pentagon), h3_h3_is_res_class_iii(h3_h3_to_parent(:pentagon)); \ No newline at end of file diff --git a/test/sql/miscellaneous.sql b/test/sql/miscellaneous.sql new file mode 100644 index 00000000..5710e4ce --- /dev/null +++ b/test/sql/miscellaneous.sql @@ -0,0 +1,10 @@ +SELECT h3_rads_to_degs(h3_degs_to_rads(90.45)); + +SELECT h3_hex_area_km2(10); +SELECT h3_hex_area_m2(10); + +SELECT h3_edge_length_km(10); +SELECT h3_edge_length_m(10); + +SELECT h3_num_hexagons(0); +SELECT h3_num_hexagons(15); diff --git a/test/sql/postgis.sql b/test/sql/postgis.sql new file mode 100644 index 00000000..94d78f94 --- /dev/null +++ b/test/sql/postgis.sql @@ -0,0 +1,9 @@ +-- Variables for testing +\set resolution 10 +\set hexagon '\'8a63a9a99047fff\'' + +SELECT h3_geo_to_h3(h3_h3_to_geo(:hexagon), :resolution); + +SELECT h3_h3_to_geo_boundary(:hexagon); + +SELECT ST_AsEWKT( h3_h3_to_geo_boundary_geometry(:hexagon)); diff --git a/test/sql/regions.sql b/test/sql/regions.sql new file mode 100644 index 00000000..8cd9b6f8 --- /dev/null +++ b/test/sql/regions.sql @@ -0,0 +1,27 @@ +-- basecell around middle +\set idx_basecell '\'8059fffffffffff\'' +-- 7 child hexes in basecell +\set idz_7hex 'ARRAY(SELECT h3_h3_to_children(:idx_basecell, 1))' +-- 6 child hexes in outer rim of basecell +\set idz_6hex 'ARRAY(SELECT id FROM (SELECT h3_h3_to_children(:idx_basecell, 1) id) q WHERE id != \'81583ffffffffff\')' + +SELECT h3_h3_get_resolution(:idx_basecell); + +-- BACK AND FORTH +SELECT :idz_7hex; +SELECT (h3_h3_set_to_linked_geo(:idz_7hex)).exterior, (h3_h3_set_to_linked_geo(:idz_7hex)).holes; +SELECT h3_polyfill(exterior, holes, 1) FROM +( + SELECT * FROM h3_h3_set_to_linked_geo(:idz_7hex) +) q; + +SELECT :idz_6hex; +SELECT (h3_h3_set_to_linked_geo(:idz_6hex)).exterior, (h3_h3_set_to_linked_geo(:idz_6hex)).holes; +SELECT h3_polyfill(exterior, holes, 1) FROM +( + SELECT * FROM h3_h3_set_to_linked_geo(:idz_6hex) +) q; + + + + diff --git a/test/sql/traversal.sql b/test/sql/traversal.sql new file mode 100644 index 00000000..4477c5a7 --- /dev/null +++ b/test/sql/traversal.sql @@ -0,0 +1,42 @@ +\set hexagon '\'880326b88dfffff\'' +\set origin '\'880326b887fffff\'' +\set pentagon '\'831c00fffffffff\'' + +SELECT h3_k_ring(:hexagon); +SELECT h3_k_ring(h3_k_ring(:hexagon)); +SELECT * FROM h3_k_ring_distances(:hexagon); + +SELECT h3_k_ring_distances(:pentagon); + +SELECT h3_k_ring(:hexagon); +SELECT h3_hex_range(:hexagon); +SELECT h3_k_ring(:hexagon, 2); +SELECT h3_hex_range(:hexagon, 2); +SELECT * FROM h3_hex_range_distances(:hexagon, 2); + +SELECT h3_hex_range(:hexagon); +SELECT h3_hex_range('880326b8ebfffff'); +SELECT h3_hex_ranges('{880326b88dfffff,880326b8ebfffff}'::h3index[]); + +SELECT h3_hex_range(:hexagon, 2); +SELECT h3_hex_ring(:hexagon, 1); +SELECT h3_hex_ring(:hexagon, 2); + +SELECT h3_h3_get_resolution(:hexagon); +SELECT h3_h3_get_resolution(h3_k_ring(:hexagon)); +SELECT h3_h3_get_resolution(h3_hex_range(:hexagon)); + +SELECT h3_k_ring(:pentagon); +SELECT h3_k_ring(h3_k_ring(:pentagon)); + +SELECT h3_h3_get_resolution(:pentagon); +SELECT h3_h3_get_resolution(h3_k_ring(:pentagon)); + +SELECT h3_h3_indexes_are_neighbors(:hexagon, '880326b8ebfffff'), h3_h3_indexes_are_neighbors('880326b881fffff', '880326b8ebfffff'); + +SELECT h3_distance('880326b881fffff', '880326b885fffff'); +SELECT h3_distance('880326b881fffff', h3_h3_to_parent('880326b885fffff')); + +SELECT h3_experimental_h3_to_local_ij(:origin, :hexagon); + +SELECT :hexagon, h3_experimental_local_ij_to_h3(:origin, h3_experimental_h3_to_local_ij(:origin, :hexagon)); \ No newline at end of file diff --git a/test/sql/type.sql b/test/sql/type.sql new file mode 100644 index 00000000..787f0e27 --- /dev/null +++ b/test/sql/type.sql @@ -0,0 +1,15 @@ +\set geometry POINT('64.7498111652365,89.5695822308866') +\set resolution 8 +\set hexagon '\'880326b88dfffff\'' +\set pentagon '\'844c001ffffffff\'' +\set pentagon_edgecross '\'831c00fffffffff\'' + +-- Type conversions +SELECT h3_string_to_h3(h3_h3_to_string(:hexagon)); +SELECT h3_h3_to_string(h3_string_to_h3(:hexagon)); + +SELECT h3_string_to_h3(:hexagon) = h3_string_to_h3(:hexagon); +SELECT h3_string_to_h3(:hexagon) = h3_string_to_h3(:pentagon); + +SELECT h3_string_to_h3(:hexagon) <> h3_string_to_h3(:hexagon); +SELECT h3_string_to_h3(:hexagon) <> h3_string_to_h3(:pentagon); diff --git a/test/sql/uniedges.sql b/test/sql/uniedges.sql new file mode 100644 index 00000000..d523d6ab --- /dev/null +++ b/test/sql/uniedges.sql @@ -0,0 +1,37 @@ +\set hexagon '\'880326b885fffff\'' +\set neighbor '\'880326b887fffff\'' +\set pentagon '\'831c00fffffffff\'' + +\set uniedge 'h3_get_h3_unidirectional_edge(:hexagon, :neighbor)' + +SELECT :uniedge; + +SELECT h3_h3_indexes_are_neighbors(:hexagon, :neighbor), TRUE expected; +SELECT h3_h3_indexes_are_neighbors(:hexagon, :hexagon), FALSE expected; + +SELECT h3_h3_unidirectional_edge_is_valid(:uniedge), TRUE expected; +SELECT h3_h3_unidirectional_edge_is_valid(:hexagon), FALSE expected; + +SELECT + h3_get_origin_h3_index_from_unidirectional_edge(:uniedge), + :hexagon expected; + +SELECT + h3_get_destination_h3_index_from_unidirectional_edge(:uniedge), + :neighbor expected; + +SELECT + h3_get_h3_indexes_from_unidirectional_edge(:uniedge), + (:hexagon, :neighbor) expected; + +SELECT array_length(array_agg(edge), 1), 6 expected FROM ( + SELECT h3_get_h3_unidirectional_edges_from_hexagon(:hexagon) edge +) q; +SELECT array_length(array_agg(edge), 1), 5 expected FROM ( + SELECT h3_get_h3_unidirectional_edges_from_hexagon(:pentagon) edge +) q; + +SELECT h3_get_origin_h3_index_from_unidirectional_edge(h3_get_h3_unidirectional_edges_from_hexagon(:hexagon)), + h3_get_destination_h3_index_from_unidirectional_edge(h3_get_h3_unidirectional_edges_from_hexagon(:hexagon)); + +SELECT h3_get_unidirectional_edge_boundary(:uniedge); \ No newline at end of file