This is an automated email from the ASF dual-hosted git repository.
Mryange pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 61179b65580 [feature](function) Add H3 cell and coordinate functions
(#69018)
61179b65580 is described below
commit 61179b655805b6ec150cbf7ef50144f8eb6f3ad6
Author: Mryange <[email protected]>
AuthorDate: Mon Oct 19 19:32:54 2026 +0800
[feature](function) Add H3 cell and coordinate functions (#69018)
### What problem does this PR solve?
Issue Number: N/A
Problem Summary: Add `h3_lat_lng_to_cell`, `h3_cell_to_lat_lng`, and
`h3_cell_to_boundary` using official H3 4.5.0. Coordinates use degrees
in latitude/longitude order. Invalid inputs return NULL; centers contain
two coordinates and boundaries preserve H3 vertex order without closing
the ring. Input NULL propagation uses the default execution framework.
FE has no separate Java H3 evaluator; constant folding through BE
remains supported.
Build H3 as a static C library from its 18 upstream core sources.
Generate its versioned API header in the build directory, export include
paths through the target, enable PIC, and inherit Doris sanitizer flags.
Only the H3 target relaxes third-party warning errors. The wrapper
avoids upstream standalone tools and global build options. Include the
upstream license attribution and NOTICE.
### Release note
Add three H3 SQL functions for mapping coordinates to cells and
retrieving cell centers and boundaries.
### Check List (For Author)
- Test
- [x] Regression test
- [x] Unit Test
- [x] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- Behavior changed:
- [ ] No.
- [x] Yes. Add three SQL functions.
- Does this need documentation?
- [ ] No.
- [x] Yes. SQL function documentation is needed; no documentation PR
yet.
Validation on Linux x86_64:
- ASAN BE build and clean FE build passed; H3 objects carry ASAN/UBSAN
flags.
- `run-be-ut.sh --run --filter=FunctionH3Test.* -j48`: all three tests
passed.
- `run-regression-test.sh --run -d
query_p0/sql_functions/spatial_functions -s test_h3`: generated results
with `-forceGenOut`, inspected them, and reran successfully. Includes
constant-folding modes, nullable/non-nullable table columns, all 16
resolutions, stored nested arrays, array expansion, filtering/grouping,
and 35,000 mixed rows.
- Compared 10,000 inputs per function against official C/Python H3
4.5.0: exact agreement. ClickHouse 26.3.9.8 (H3 3.7.1) matched cell IDs
and coordinates within tolerance (maximum coordinate error 8.29e-8
degrees).
- Clang-format 16 and targeted clang-tidy checks passed; no sanitizer
diagnostics during these tests. ARM/macOS builds were not run.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
---
.gitmodules | 3 +
LICENSE.txt | 1 +
NOTICE.txt | 19 ++
be/CMakeLists.txt | 3 +
be/src/exprs/CMakeLists.txt | 4 +-
be/src/exprs/function/geo/functions_geo.cpp | 3 +
be/src/exprs/function/geo/functions_h3.cpp | 145 +++++++++
be/test/exprs/function/function_h3_test.cpp | 164 ++++++++++
build.sh | 1 +
contrib/h3 | 1 +
contrib/h3-cmake/CMakeLists.txt | 67 ++++
.../doris/catalog/BuiltinScalarFunctions.java | 6 +
.../functions/scalar/H3CellToBoundary.java | 68 ++++
.../functions/scalar/H3CellToLatLng.java | 68 ++++
.../functions/scalar/H3LatLngToCell.java | 68 ++++
.../expressions/visitor/ScalarFunctionVisitor.java | 15 +
.../sql_functions/spatial_functions/test_h3.out | 342 +++++++++++++++++++++
.../sql_functions/spatial_functions/test_h3.groovy | 188 +++++++++++
run-be-ut.sh | 4 +-
19 files changed, 1167 insertions(+), 3 deletions(-)
diff --git a/.gitmodules b/.gitmodules
index d4e96b80914..8969d2dba3f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -32,6 +32,9 @@
[submodule "contrib/datasketches-cpp"]
path = contrib/datasketches-cpp
url = https://github.com/apache/datasketches-cpp.git
+[submodule "contrib/h3"]
+ path = contrib/h3
+ url = https://github.com/uber/h3.git
[submodule "doris-skills"]
path = doris-skills
url = https://github.com/apache/doris-skills
diff --git a/LICENSE.txt b/LICENSE.txt
index df1f548b6ae..b54c0b65503 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -207,6 +207,7 @@ The text of each license is the standard Apache 2.0 license.
software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder from AWS
SDK v2 (sdk-core 2.29.52)
org.apache.hadoop.fs.FileSystem from Apache Hadoop (hadoop-common 3.4.2)
+H3 4.5.0 (contrib/h3) from https://github.com/uber/h3
--------------------------------------------------------------------------------
diff --git a/NOTICE.txt b/NOTICE.txt
index c1ad754dc72..1cf02043409 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -85,3 +85,22 @@ The licenses for these third party components are included
in LICENSE.txt
This product includes software developed at
The Apache Software Foundation (https://www.apache.org/).
+
+--------------------------------------------------------------------------------
+
+This product includes H3 (https://github.com/uber/h3), which includes the
+following in its NOTICE file:
+
+Copyright 2017-2021 Uber Technologies, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/be/CMakeLists.txt b/be/CMakeLists.txt
index 8a36b1f8ac0..13f6ebe986c 100644
--- a/be/CMakeLists.txt
+++ b/be/CMakeLists.txt
@@ -605,6 +605,8 @@ set(CMAKE_CXX_FLAGS "${CXX_COMMON_FLAGS} ${CMAKE_CXX_FLAGS}
${EXTRA_CXX_FLAGS}")
set(CMAKE_C_FLAGS ${CMAKE_CXX_FLAGS})
+add_subdirectory(${CONTRIB_PATH}/h3-cmake ${PROJECT_BINARY_DIR}/h3)
+
# Set include dirs
include_directories(
${CONTRIB_PATH}/apache-orc/c++/include
@@ -768,6 +770,7 @@ set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} ic)
set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} clucene-core-static)
set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} clucene-shared-static)
set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} clucene-contribs-lib)
+set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} h3)
set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} ${WL_END_GROUP})
diff --git a/be/src/exprs/CMakeLists.txt b/be/src/exprs/CMakeLists.txt
index 1940ba6d8e4..3965155e4a6 100644
--- a/be/src/exprs/CMakeLists.txt
+++ b/be/src/exprs/CMakeLists.txt
@@ -33,7 +33,7 @@ add_library(Exprs STATIC ${SRC_FILES})
# function_array_distance uses faiss headers (platform_macros.h, distances.h),
# which are exported by ann_index via PUBLIC linkage with faiss.
target_link_libraries(Exprs
- PRIVATE ann_index
+ PRIVATE ann_index h3
PUBLIC DataSketches::HLL)
pch_reuse(Exprs)
@@ -122,4 +122,4 @@ set(WARNING_OPTION "-Wno-unused-but-set-variable")
check_cxx_compiler_flag(${WARNING_OPTION} HAS_WARNING_OPTION)
if (HAS_WARNING_OPTION)
target_compile_options(Exprs PRIVATE ${WARNING_OPTION}
"-Wno-unused-macros")
-endif()
\ No newline at end of file
+endif()
diff --git a/be/src/exprs/function/geo/functions_geo.cpp
b/be/src/exprs/function/geo/functions_geo.cpp
index 8f53b884eb9..05e59749b1e 100644
--- a/be/src/exprs/function/geo/functions_geo.cpp
+++ b/be/src/exprs/function/geo/functions_geo.cpp
@@ -1124,7 +1124,10 @@ struct StNumPoints {
}
};
+void register_function_h3(SimpleFunctionFactory& factory);
+
void register_function_geo(SimpleFunctionFactory& factory) {
+ register_function_h3(factory);
factory.register_function<GeoFunction<StPoint>>();
factory.register_function<GeoFunction<StAsText<StAsWktName>>>();
factory.register_function<GeoFunction<StAsText<StAsTextName>>>();
diff --git a/be/src/exprs/function/geo/functions_h3.cpp
b/be/src/exprs/function/geo/functions_h3.cpp
new file mode 100644
index 00000000000..1a381f5c642
--- /dev/null
+++ b/be/src/exprs/function/geo/functions_h3.cpp
@@ -0,0 +1,145 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 <h3api.h>
+
+#include <cmath>
+
+#include "core/block/block.h"
+#include "core/column/column_array.h"
+#include "core/column/column_execute_util.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/data_type/data_type_number.h"
+#include "exprs/function/simple_function_factory.h"
+
+namespace doris {
+
+class FunctionH3LatLngToCell : public IFunction {
+public:
+ static constexpr auto name = "h3_lat_lng_to_cell";
+ static FunctionPtr create() { return
std::make_shared<FunctionH3LatLngToCell>(); }
+ String get_name() const override { return name; }
+ size_t get_number_of_arguments() const override { return 3; }
+
+ DataTypePtr get_return_type_impl(const DataTypes&) const override {
+ return make_nullable(std::make_shared<DataTypeInt64>());
+ }
+
+ Status execute_impl(FunctionContext*, Block& block, const ColumnNumbers&
arguments,
+ uint32_t result, size_t rows) const override {
+ auto lat =
ColumnView<TYPE_DOUBLE>::create(block.get_by_position(arguments[0]).column);
+ auto lng =
ColumnView<TYPE_DOUBLE>::create(block.get_by_position(arguments[1]).column);
+ auto resolution =
ColumnView<TYPE_INT>::create(block.get_by_position(arguments[2]).column);
+ auto values = ColumnInt64::create(rows, 0);
+ auto nulls = ColumnUInt8::create(rows, 0);
+ for (size_t row = 0; row < rows; ++row) {
+ const double latitude = lat.value_at(row);
+ const double longitude = lng.value_at(row);
+ const int res = resolution.value_at(row);
+ if (!std::isfinite(latitude) || !std::isfinite(longitude) ||
latitude < -90 ||
+ latitude > 90 || longitude < -180 || longitude > 180 || res <
0 || res > 15) {
+ nulls->get_data()[row] = 1;
+ continue;
+ }
+ const LatLng coord {degsToRads(latitude), degsToRads(longitude)};
+ H3Index cell = 0;
+ if (latLngToCell(&coord, res, &cell) != E_SUCCESS) {
+ nulls->get_data()[row] = 1;
+ continue;
+ }
+ values->get_data()[row] = static_cast<Int64>(cell);
+ }
+ block.replace_by_position(result,
+ ColumnNullable::create(std::move(values),
std::move(nulls)));
+ return Status::OK();
+ }
+};
+
+template <bool boundary>
+class FunctionH3CellToCoordinates : public IFunction {
+public:
+ static constexpr auto name = boundary ? "h3_cell_to_boundary" :
"h3_cell_to_lat_lng";
+ static FunctionPtr create() { return
std::make_shared<FunctionH3CellToCoordinates>(); }
+ String get_name() const override { return name; }
+ size_t get_number_of_arguments() const override { return 1; }
+
+ DataTypePtr get_return_type_impl(const DataTypes&) const override {
+ DataTypePtr type =
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeFloat64>()));
+ if constexpr (boundary) {
+ type = std::make_shared<DataTypeArray>(make_nullable(type));
+ }
+ return make_nullable(type);
+ }
+
+ Status execute_impl(FunctionContext*, Block& block, const ColumnNumbers&
arguments,
+ uint32_t result, size_t rows) const override {
+ auto cells =
ColumnView<TYPE_BIGINT>::create(block.get_by_position(arguments[0]).column);
+ auto values = ColumnFloat64::create();
+ auto point_offsets = ColumnArray::ColumnOffsets::create();
+ auto row_offsets = ColumnArray::ColumnOffsets::create();
+ auto nulls = ColumnUInt8::create(rows, 0);
+ row_offsets->reserve(rows);
+ if constexpr (!boundary) {
+ values->reserve(2 * rows);
+ }
+ for (size_t row = 0; row < rows; ++row) {
+ CellBoundary coords {};
+ const auto cell = cells.value_at(row);
+ bool valid = cell > 0 && isValidCell(static_cast<H3Index>(cell));
+ if (valid) {
+ if constexpr (boundary) {
+ valid = cellToBoundary(cell, &coords) == E_SUCCESS;
+ } else {
+ valid = cellToLatLng(cell, &coords.verts[0]) == E_SUCCESS;
+ coords.numVerts = 1;
+ }
+ }
+ if (!valid) {
+ nulls->get_data()[row] = 1;
+ } else {
+ for (int vertex = 0; vertex < coords.numVerts; ++vertex) {
+
values->get_data().push_back(radsToDegs(coords.verts[vertex].lat));
+
values->get_data().push_back(radsToDegs(coords.verts[vertex].lng));
+ if constexpr (boundary) {
+ point_offsets->get_data().push_back(values->size());
+ }
+ }
+ }
+ row_offsets->get_data().push_back(boundary ? point_offsets->size()
: values->size());
+ }
+ auto element_nulls = ColumnUInt8::create(values->size(), 0);
+ ColumnPtr data = ColumnNullable::create(std::move(values),
std::move(element_nulls));
+ if constexpr (boundary) {
+ data = ColumnArray::create(data, std::move(point_offsets));
+ data = ColumnNullable::create(data,
ColumnUInt8::create(data->size(), 0));
+ }
+ data = ColumnArray::create(data, std::move(row_offsets));
+ block.replace_by_position(result, ColumnNullable::create(data,
std::move(nulls)));
+ return Status::OK();
+ }
+};
+
+void register_function_h3(SimpleFunctionFactory& factory) {
+ factory.register_function<FunctionH3LatLngToCell>();
+ factory.register_function<FunctionH3CellToCoordinates<false>>();
+ factory.register_function<FunctionH3CellToCoordinates<true>>();
+}
+
+} // namespace doris
diff --git a/be/test/exprs/function/function_h3_test.cpp
b/be/test/exprs/function/function_h3_test.cpp
new file mode 100644
index 00000000000..3c3ed851df0
--- /dev/null
+++ b/be/test/exprs/function/function_h3_test.cpp
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 <limits>
+
+#include "core/column/column_const.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_nothing.h"
+#include "core/data_type/data_type_number.h"
+#include "exprs/function/function_test_util.h"
+
+namespace doris {
+using namespace ut_type;
+
+TEST(FunctionH3Test, LatLngToCell) {
+ const InputTypeSet types = {TYPE_DOUBLE, TYPE_DOUBLE, TYPE_INT};
+ const DataSet cases = {{{55.71290588, 37.79506683, int32_t(15)},
int64_t(644325524701193974)},
+ {{91.0, 0.0, int32_t(9)}, Null()},
+ {{-91.0, 0.0, int32_t(9)}, Null()},
+ {{0.0, 181.0, int32_t(9)}, Null()},
+ {{0.0, -181.0, int32_t(9)}, Null()},
+ {{0.0, 0.0, int32_t(-1)}, Null()},
+ {{0.0, 0.0, int32_t(16)}, Null()},
+ {{std::numeric_limits<double>::quiet_NaN(), 0.0,
int32_t(9)}, Null()},
+ {{0.0, std::numeric_limits<double>::quiet_NaN(),
int32_t(9)}, Null()},
+ {{0.0, std::numeric_limits<double>::infinity(),
int32_t(9)}, Null()},
+ {{-std::numeric_limits<double>::infinity(), 0.0,
int32_t(9)}, Null()},
+ {{Null(), 0.0, int32_t(9)}, Null()},
+ {{0.0, Null(), int32_t(9)}, Null()},
+ {{0.0, 0.0, Null()}, Null()}};
+ check_function_all_arg_comb<DataTypeInt64, true>("h3_lat_lng_to_cell",
types, cases);
+}
+
+// GTest assertion macros dominate the reported complexity of these checks.
+// NOLINTBEGIN(readability-function-cognitive-complexity)
+TEST(FunctionH3Test, OnlyNullLatLngToCell) {
+ const size_t rows = 7;
+ for (size_t null_argument = 0; null_argument < 3; ++null_argument) {
+ DataTypes types = {std::make_shared<DataTypeFloat64>(),
std::make_shared<DataTypeFloat64>(),
+ std::make_shared<DataTypeInt32>()};
+ types[null_argument] =
make_nullable(std::make_shared<DataTypeNothing>());
+ Block block;
+ for (const auto& type : types) {
+ block.insert({type->create_column_const_with_default_value(rows),
type, "argument"});
+ }
+ auto result_type = make_nullable(std::make_shared<DataTypeInt64>());
+ auto function = SimpleFunctionFactory::instance().get_function(
+ "h3_lat_lng_to_cell", block.get_columns_with_type_and_name(),
result_type);
+ ASSERT_NE(function, nullptr);
+ FunctionUtils utils(result_type, types, false);
+ auto* context = utils.get_fn_ctx();
+ ASSERT_TRUE(function->open(context,
FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(function->open(context,
FunctionContext::THREAD_LOCAL).ok());
+ block.insert({nullptr, result_type, "result"});
+ ASSERT_TRUE(function->execute(context, block, {0, 1, 2}, 3,
rows).ok());
+ EXPECT_EQ(block.get_by_position(3).column->size(), rows);
+ EXPECT_TRUE(block.get_by_position(3).column->only_null());
+ EXPECT_TRUE(function->close(context,
FunctionContext::THREAD_LOCAL).ok());
+ EXPECT_TRUE(function->close(context,
FunctionContext::FRAGMENT_LOCAL).ok());
+ }
+}
+
+static void check_coordinates(bool boundary, bool constant, bool only_null,
size_t rows) {
+ DataTypePtr input_type =
+ make_nullable(only_null ?
DataTypePtr(std::make_shared<DataTypeNothing>())
+ :
DataTypePtr(std::make_shared<DataTypeInt64>()));
+ DataTypePtr output_type =
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeFloat64>()));
+ if (boundary) {
+ output_type =
std::make_shared<DataTypeArray>(make_nullable(output_type));
+ }
+ output_type = make_nullable(output_type);
+ auto input = input_type->create_column();
+ const Int64 cells[] = {644325524701193974, 0, 581109487465660415, -1,
576636674163867647};
+ for (size_t i = 0; i < (constant ? 1 : rows); ++i) {
+ if (only_null || i % 6 == 5) {
+ input->insert(Field());
+ } else {
+ input->insert(Field::create_field<TYPE_BIGINT>(cells[i % 6]));
+ }
+ }
+ if (constant) {
+ input = ColumnConst::create(std::move(input), rows);
+ }
+ Block block;
+ block.insert({std::move(input), input_type, "cell"});
+ auto function = SimpleFunctionFactory::instance().get_function(
+ boundary ? "h3_cell_to_boundary" : "h3_cell_to_lat_lng",
+ block.get_columns_with_type_and_name(), output_type);
+ ASSERT_NE(function, nullptr);
+ FunctionUtils utils(output_type, {input_type}, false);
+ auto* context = utils.get_fn_ctx();
+ std::vector<std::shared_ptr<ColumnPtrWrapper>> constants(1);
+ if (constant) {
+ constants[0] =
std::make_shared<ColumnPtrWrapper>(block.get_by_position(0).column);
+ }
+ context->set_constant_cols(constants);
+ ASSERT_TRUE(function->open(context, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(function->open(context, FunctionContext::THREAD_LOCAL).ok());
+ block.insert({nullptr, output_type, "result"});
+ ASSERT_TRUE(function->execute(context, block, {0}, 1, rows).ok());
+ auto result =
block.get_by_position(1).column->convert_to_full_column_if_const();
+ ASSERT_EQ(result->size(), rows);
+ for (size_t i = 0; i < rows; ++i) {
+ Field value;
+ result->get(i, value);
+ const auto index = constant ? 0 : i % 6;
+ if (only_null || index == 1 || index == 3 || index == 5) {
+ EXPECT_TRUE(value.is_null());
+ continue;
+ }
+ ASSERT_FALSE(value.is_null());
+ const auto& array = value.get<TYPE_ARRAY>();
+ ASSERT_EQ(array.size(), boundary ? (index == 2 ? 10 : index == 4 ? 5 :
6) : 2);
+ if (index == 0) {
+ if (boundary) {
+ const double expected[][2] = {{55.712900225355526,
37.79505811173474},
+ {55.71289713485416,
37.79506506997184},
+ {55.712899340954856,
37.79507312653982},
+ {55.71290463755745,
37.79507422487167},
+ {55.71290772805917,
37.79506726663346},
+ {55.712905521957914,
37.795059210064515}};
+ for (size_t vertex = 0; vertex < array.size(); ++vertex) {
+ const auto& point = array[vertex].get<TYPE_ARRAY>();
+ ASSERT_EQ(point.size(), 2);
+ EXPECT_NEAR(point[0].get<TYPE_DOUBLE>(),
expected[vertex][0], 1e-10);
+ EXPECT_NEAR(point[1].get<TYPE_DOUBLE>(),
expected[vertex][1], 1e-10);
+ }
+ } else {
+ EXPECT_NEAR(array[0].get<TYPE_DOUBLE>(), 55.71290243145668,
1e-10);
+ EXPECT_NEAR(array[1].get<TYPE_DOUBLE>(), 37.795066168302505,
1e-10);
+ }
+ }
+ }
+ EXPECT_TRUE(function->close(context, FunctionContext::THREAD_LOCAL).ok());
+ EXPECT_TRUE(function->close(context,
FunctionContext::FRAGMENT_LOCAL).ok());
+}
+// NOLINTEND(readability-function-cognitive-complexity)
+
+TEST(FunctionH3Test, ReverseCoordinates) {
+ for (bool boundary : {false, true}) {
+ check_coordinates(boundary, false, false, 8193);
+ check_coordinates(boundary, true, false, 7);
+ check_coordinates(boundary, true, true, 7);
+ check_coordinates(boundary, false, true, 7);
+ check_coordinates(boundary, false, false, 0);
+ }
+}
+
+} // namespace doris
diff --git a/build.sh b/build.sh
index a33f55c294e..6a5044fdf62 100755
--- a/build.sh
+++ b/build.sh
@@ -958,6 +958,7 @@ if [[ "${BUILD_BE}" -eq 1 ]]; then
update_submodule "contrib/clucene" "clucene"
"https://github.com/apache/doris-thirdparty/archive/refs/heads/clucene.tar.gz"
update_submodule "contrib/openblas" "openblas"
"https://github.com/apache/doris-thirdparty/archive/refs/heads/openblas.tar.gz"
update_submodule "contrib/faiss" "faiss"
"https://github.com/apache/doris-thirdparty/archive/refs/heads/faiss.tar.gz"
+ update_submodule "contrib/h3" "h3"
"https://github.com/uber/h3/archive/refs/tags/v4.5.0.tar.gz"
if [[ "${COMPILE_BENCH}" -eq 1 ]]; then
compile_bench_phase_end
fi
diff --git a/contrib/h3 b/contrib/h3
new file mode 160000
index 00000000000..1b536c34225
--- /dev/null
+++ b/contrib/h3
@@ -0,0 +1 @@
+Subproject commit 1b536c34225191ba24a75a840f634d4a48c3b206
diff --git a/contrib/h3-cmake/CMakeLists.txt b/contrib/h3-cmake/CMakeLists.txt
new file mode 100644
index 00000000000..41bb3efa4c0
--- /dev/null
+++ b/contrib/h3-cmake/CMakeLists.txt
@@ -0,0 +1,67 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+set(H3_SOURCE_DIR "${CONTRIB_PATH}/h3")
+set(H3_BINARY_DIR "${PROJECT_BINARY_DIR}/h3")
+
+file(STRINGS "${H3_SOURCE_DIR}/VERSION" H3_VERSION LIMIT_COUNT 1)
+string(REPLACE "." ";" H3_VERSION_PARTS "${H3_VERSION}")
+list(GET H3_VERSION_PARTS 0 H3_VERSION_MAJOR)
+list(GET H3_VERSION_PARTS 1 H3_VERSION_MINOR)
+list(GET H3_VERSION_PARTS 2 H3_VERSION_PATCH)
+
+set(H3_SOURCES
+ ${H3_SOURCE_DIR}/src/h3lib/lib/algos.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/area.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/baseCells.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/bbox.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/cellsToMultiPoly.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/directedEdge.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/faceijk.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/h3Assert.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/h3Index.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/iterators.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/latLng.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/linkedGeo.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/localij.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/mathExtensions.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/polygon.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/polyfill.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/vec2d.c
+ ${H3_SOURCE_DIR}/src/h3lib/lib/vertex.c)
+
+file(MAKE_DIRECTORY "${H3_BINARY_DIR}/include")
+configure_file(
+ ${H3_SOURCE_DIR}/src/h3lib/include/h3api.h.in
+ ${H3_BINARY_DIR}/include/h3api.h)
+
+add_library(h3 STATIC ${H3_SOURCES})
+set_target_properties(h3 PROPERTIES POSITION_INDEPENDENT_CODE ON)
+target_compile_features(h3 PUBLIC c_std_99)
+# Keep upstream warnings visible without imposing Doris warning policy.
+target_compile_options(h3 PRIVATE -Wno-error)
+# Upstream uses empty initializers as a C extension before C23.
+if(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ target_compile_options(h3 PRIVATE -Wno-c23-extensions)
+endif()
+target_compile_definitions(h3 PRIVATE BUILDING_H3=1)
+target_include_directories(
+ h3
+ SYSTEM PUBLIC
+ $<BUILD_INTERFACE:${H3_SOURCE_DIR}/src/h3lib/include>
+ $<BUILD_INTERFACE:${H3_BINARY_DIR}/include>)
+target_link_libraries(h3 PUBLIC m)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
index be0802713db..7f0140e8303 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
@@ -239,6 +239,9 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.GetVariantTyp
import org.apache.doris.nereids.trees.expressions.functions.scalar.Greatest;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Grouping;
import org.apache.doris.nereids.trees.expressions.functions.scalar.GroupingId;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.H3CellToBoundary;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.H3CellToLatLng;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.H3LatLngToCell;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.HammingDistance;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Hex;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.HllCardinality;
@@ -1130,6 +1133,9 @@ public class BuiltinScalarFunctions implements
FunctionHelper {
scalar(StLinefromtext.class, "st_linefromtext"),
scalar(StLinestringfromtext.class, "st_linestringfromtext"),
scalar(StPoint.class, "st_point"),
+ scalar(H3CellToBoundary.class, "h3_cell_to_boundary"),
+ scalar(H3CellToLatLng.class, "h3_cell_to_lat_lng"),
+ scalar(H3LatLngToCell.class, "h3_lat_lng_to_cell"),
scalar(StPolyfromtext.class, "st_polyfromtext"),
scalar(StPolygon.class, "st_polygon"),
scalar(StPolygonfromtext.class, "st_polygonfromtext"),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3CellToBoundary.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3CellToBoundary.java
new file mode 100644
index 00000000000..effc7619223
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3CellToBoundary.java
@@ -0,0 +1,68 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.scalar;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
+import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
+import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.BigIntType;
+import org.apache.doris.nereids.types.DoubleType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/** H3 scalar function 'h3_cell_to_boundary'. */
+public class H3CellToBoundary extends ScalarFunction
+ implements UnaryExpression, ExplicitlyCastableSignature,
AlwaysNullable, PropagateNullLiteral {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+
FunctionSignature.ret(ArrayType.of(ArrayType.of(DoubleType.INSTANCE)))
+ .args(BigIntType.INSTANCE)
+ );
+
+ public H3CellToBoundary(Expression arg0) {
+ super("h3_cell_to_boundary", arg0);
+ }
+
+ private H3CellToBoundary(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ @Override
+ public H3CellToBoundary withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 1);
+ return new H3CellToBoundary(getFunctionParams(children));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitH3CellToBoundary(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3CellToLatLng.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3CellToLatLng.java
new file mode 100644
index 00000000000..bc55fa3153c
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3CellToLatLng.java
@@ -0,0 +1,68 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.scalar;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
+import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
+import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.BigIntType;
+import org.apache.doris.nereids.types.DoubleType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/** H3 scalar function 'h3_cell_to_lat_lng'. */
+public class H3CellToLatLng extends ScalarFunction
+ implements UnaryExpression, ExplicitlyCastableSignature,
AlwaysNullable, PropagateNullLiteral {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+ FunctionSignature.ret(ArrayType.of(DoubleType.INSTANCE))
+ .args(BigIntType.INSTANCE)
+ );
+
+ public H3CellToLatLng(Expression arg0) {
+ super("h3_cell_to_lat_lng", arg0);
+ }
+
+ private H3CellToLatLng(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ @Override
+ public H3CellToLatLng withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 1);
+ return new H3CellToLatLng(getFunctionParams(children));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitH3CellToLatLng(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3LatLngToCell.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3LatLngToCell.java
new file mode 100644
index 00000000000..4867faa6bae
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/H3LatLngToCell.java
@@ -0,0 +1,68 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.scalar;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
+import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
+import org.apache.doris.nereids.trees.expressions.shape.TernaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.BigIntType;
+import org.apache.doris.nereids.types.DoubleType;
+import org.apache.doris.nereids.types.IntegerType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/** H3 scalar function 'h3_lat_lng_to_cell'. */
+public class H3LatLngToCell extends ScalarFunction
+ implements TernaryExpression, ExplicitlyCastableSignature,
AlwaysNullable, PropagateNullLiteral {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+ FunctionSignature.ret(BigIntType.INSTANCE)
+ .args(DoubleType.INSTANCE, DoubleType.INSTANCE,
IntegerType.INSTANCE)
+ );
+
+ public H3LatLngToCell(Expression arg0, Expression arg1, Expression arg2) {
+ super("h3_lat_lng_to_cell", arg0, arg1, arg2);
+ }
+
+ private H3LatLngToCell(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ @Override
+ public H3LatLngToCell withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 3);
+ return new H3LatLngToCell(getFunctionParams(children));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitH3LatLngToCell(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
index 9d5e1b7485e..865669a0632 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
@@ -252,6 +252,9 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.Gcd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.GetFormat;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.GetVariantType;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Greatest;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.H3CellToBoundary;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.H3CellToLatLng;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.H3LatLngToCell;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.HammingDistance;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Hex;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.HllCardinality;
@@ -2519,6 +2522,18 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(stPoint, context);
}
+ default R visitH3CellToBoundary(H3CellToBoundary function, C context) {
+ return visitScalarFunction(function, context);
+ }
+
+ default R visitH3CellToLatLng(H3CellToLatLng function, C context) {
+ return visitScalarFunction(function, context);
+ }
+
+ default R visitH3LatLngToCell(H3LatLngToCell function, C context) {
+ return visitScalarFunction(function, context);
+ }
+
default R visitStPolyfromtext(StPolyfromtext stPolyfromtext, C context) {
return visitScalarFunction(stPolyfromtext, context);
}
diff --git
a/regression-test/data/query_p0/sql_functions/spatial_functions/test_h3.out
b/regression-test/data/query_p0/sql_functions/spatial_functions/test_h3.out
new file mode 100644
index 00000000000..7e93c2f9a47
--- /dev/null
+++ b/regression-test/data/query_p0/sql_functions/spatial_functions/test_h3.out
@@ -0,0 +1,342 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !fold_cell --
+644325524701193974 644325524701193974
+
+-- !fold_coordinates --
+[55.712902431, 37.795066168] [[55.712900225, 37.795058112], [55.712897135,
37.79506507], [55.712899341, 37.795073127], [55.712904638, 37.795074225],
[55.712907728, 37.795067267], [55.712905522, 37.79505921]]
+
+-- !fold_nested --
+644325524701193974 2 6
+
+-- !fold_null --
+\N \N \N \N \N \N
+
+-- !fold_invalid --
+\N \N \N \N \N \N
+
+-- !fold_endpoints --
+576495936675512319 648282192095284420 646243831017778096
646243831017778096
+
+-- !fold_cell --
+644325524701193974 644325524701193974
+
+-- !fold_coordinates --
+[55.712902431, 37.795066168] [[55.712900225, 37.795058112], [55.712897135,
37.79506507], [55.712899341, 37.795073127], [55.712904638, 37.795074225],
[55.712907728, 37.795067267], [55.712905522, 37.79505921]]
+
+-- !fold_nested --
+644325524701193974 2 6
+
+-- !fold_null --
+\N \N \N \N \N \N
+
+-- !fold_invalid --
+\N \N \N \N \N \N
+
+-- !fold_endpoints --
+576495936675512319 648282192095284420 646243831017778096
646243831017778096
+
+-- !cell --
+644325524701193974
+
+-- !center --
+[55.712902431, 37.795066168]
+
+-- !boundary --
+[[55.712900225, 37.795058112], [55.712897135, 37.79506507], [55.712899341,
37.795073127], [55.712904638, 37.795074225], [55.712907728, 37.795067267],
[55.712905522, 37.79505921]]
+
+-- !invalid --
+\N \N \N \N \N \N \N
+
+-- !null --
+\N \N \N \N \N
+
+-- !non_cell --
+\N \N \N \N \N
+
+-- !pentagons --
+5 10 [[63.327061328, 4.012620898], [61.890838475, 8.644221198],
[61.5405146, 11.080660058], [62.889968358, 15.771773841], [63.800792653,
17.535446308], [66.327261733, 16.433696997], [67.351768675, 14.813658726],
[67.468427885, 8.130261032], [67.015632628, 5.23925888], [64.525608422,
3.69993326]]
+
+-- !rows --
+1 644325524701193974 [55.712902431, 37.795066168] [[55.712900225,
37.795058112], [55.712897135, 37.79506507], [55.712899341, 37.795073127],
[55.712904638, 37.795074225], [55.712907728, 37.795067267], [55.712905522,
37.79505921]]
+2 \N \N \N
+3 \N \N \N
+4 \N \N \N
+5 \N \N \N
+6 576495936675512319 [48.758349724, 18.303044481] [[55.706768465,
5.523646549], [45.18424869, 2.026568965], [37.523712366, 13.244313476],
[39.643310548, 28.846868847], [49.779342868, 36.116723632], [58.40154487,
25.082722327]]
+7 648282192095284420 [79.24239851, 38.023407008] [[68.929957882,
31.831280499], [69.39359649, 62.345344957], [76.16304283, 94.143090102],
[87.364695323, 145.558197691], [81.27137179, -34.75841798], [73.310223685,
0.325610352]]
+
+-- !mixed --
+1 617440756014514175 646165426115021650
+2 \N 619056821840379903
+3 \N 619056821840379903
+4 619056821840379903 \N
+5 619056821840379903 \N
+6 617048546304851967 578712552117108735
+7 621260594331254783 646243831017778096
+
+-- !array_access --
+1 2 55.712902431 37.795066168 \N 55.712900225
37.795058112
+2 \N \N \N \N \N \N
+3 \N \N \N \N \N \N
+4 \N \N \N \N \N \N
+5 \N \N \N \N \N \N
+6 2 48.758349724 18.303044481 \N 55.706768465
5.523646549
+7 2 79.24239851 38.023407008 \N 68.929957882
31.831280499
+
+-- !null_columns --
+1 \N \N \N
+2 \N \N \N
+3 \N \N \N
+4 \N \N \N
+5 \N \N \N
+6 \N \N \N
+7 \N \N \N
+
+-- !empty --
+
+-- !roundtrip --
+644325524701193974
+
+-- !many_rows --
+10000 0 30000 10000
+
+-- !all_resolutions --
+0 0 578536630256664575 2 5 true
+1 0 576777411652222975 2 6 true
+10 1 581021526535438335 2 6 true
+100 12 632733032135666687 2 6 true
+101 12 632733032135666687 2 6 true
+102 12 631210968839402495 2 6 true
+103 12 633847439693631999 2 6 true
+104 13 637071220349785855 2 6 true
+105 13 635318325446452991 2 6 true
+106 13 635062944814269375 2 6 true
+107 13 639274992840543487 2 6 true
+108 13 637236631763037119 2 6 true
+109 13 637236631763037119 2 6 true
+11 1 585234855093075967 2 6 true
+110 13 635714568466772927 2 6 true
+111 13 638351039321002175 2 6 true
+112 14 641574819977156319 2 6 true
+113 14 639821925073823479 2 6 true
+114 14 639566544441639839 2 6 true
+115 14 643778592467913927 2 6 true
+116 14 641740231390407607 2 6 true
+117 14 641740231390407607 2 6 true
+118 14 640218168094143383 2 6 true
+119 14 642854638948372655 2 6 true
+12 1 583194161511923711 2 7 true
+120 15 646078419604526808 2 6 true
+121 15 644325524701193974 2 6 true
+122 15 644070144069010329 2 6 true
+123 15 648282192095284420 2 6 true
+124 15 646243831017778096 2 6 true
+125 15 646243831017778096 2 6 true
+126 15 644721767721513872 2 6 true
+127 15 647358238575743144 2 6 true
+13 1 583194161511923711 2 7 true
+14 1 581672437419081727 2 6 true
+15 1 584311265325744127 2 6 true
+16 2 587531734883500031 2 6 true
+17 2 585779113348825087 2 6 true
+18 2 585523476895367167 2 6 true
+19 2 589735705941377023 2 6 true
+2 0 576495936675512319 2 6 true
+20 2 587697211383480319 2 6 true
+21 2 587697211383480319 2 6 true
+22 2 586175487290638335 2 6 true
+23 2 588811566418231295 2 6 true
+24 3 592035265791393791 2 6 true
+25 3 590282369378811903 2 6 true
+26 3 590027007803260927 2 6 true
+27 3 594239030690840575 2 6 true
+28 3 592200673571897343 2 6 true
+29 3 592200673571897343 2 6 true
+3 0 580753245698260991 2 6 true
+30 3 590678605881671679 2 6 true
+31 3 593315097326125055 2 6 true
+32 4 596538564771053567 2 6 true
+33 4 594785934646444031 2 6 true
+34 4 594530555891023871 2 6 true
+35 4 598742604548407295 2 6 true
+36 4 596704238839529471 2 6 true
+37 4 596704238839529471 2 6 true
+38 4 595182179739238399 2 6 true
+39 4 597818645413887999 2 6 true
+4 0 578712552117108735 2 5 true
+40 5 601042424243945471 2 6 true
+41 5 599289528905105407 2 6 true
+42 5 599034148002201599 2 6 true
+43 5 603246196659585023 2 6 true
+44 5 601207835245674495 2 6 true
+45 5 601207835245674495 2 6 true
+46 5 599685771850416127 2 6 true
+47 5 602322242893774847 2 6 true
+48 6 605546023066009599 2 6 true
+49 6 603793128129822719 2 6 true
+5 0 578712552117108735 2 5 true
+50 6 603537747495354367 2 6 true
+51 6 607749795481649151 2 6 true
+52 6 605711434470391807 2 6 true
+53 6 605711434470391807 2 6 true
+54 6 604189371209351167 2 6 true
+55 6 606825841984274431 2 6 true
+56 7 610049622659825663 2 6 true
+57 7 608296727690084351 2 6 true
+58 7 608041347055615999 2 6 true
+59 7 612253395092242431 2 6 true
+6 0 577199624117288959 2 6 true
+60 7 610215034013876223 2 6 true
+61 7 610215034013876223 2 6 true
+62 7 608692970719281151 2 6 true
+63 7 611329441561313279 2 6 true
+64 8 614553222213795839 2 6 true
+65 8 612800327311163391 2 6 true
+66 8 612544946678792191 2 6 true
+67 8 616756994704932863 2 6 true
+68 8 614718633626566655 2 6 true
+69 8 614718633626566655 2 6 true
+7 0 579838452023951359 2 6 true
+70 8 613196570331971583 2 6 true
+71 8 615833041184489471 2 6 true
+72 9 619056821840379903 2 6 true
+73 9 617303926937223167 2 6 true
+74 9 617048546304851967 2 6 true
+75 9 621260594331254783 2 6 true
+76 9 619222233253675007 2 6 true
+77 9 619222233253675007 2 6 true
+78 9 617700169957507071 2 6 true
+79 9 620336640811597823 2 6 true
+8 1 583031433791012863 2 7 true
+80 10 623560421467684863 2 6 true
+81 10 621807526564364287 2 6 true
+82 10 621552145932189695 2 6 true
+83 10 625764193958461439 2 6 true
+84 10 623725832880947199 2 6 true
+85 10 623725832880947199 2 6 true
+86 10 622203769584680959 2 6 true
+87 10 624840240438902783 2 6 true
+88 11 628064021095030783 2 6 true
+89 11 626311126191714303 2 6 true
+9 1 581276613233082367 2 7 true
+90 11 626055745559531519 2 6 true
+91 11 630267793585803263 2 6 true
+92 11 628229432508297215 2 6 true
+93 11 628229432508297215 2 6 true
+94 11 626707369212035071 2 6 true
+95 11 629343840066265087 2 6 true
+96 12 632567620722400767 2 6 true
+97 12 630814725819082751 2 6 true
+98 12 630559345186898943 2 6 true
+99 12 634771393213173247 2 6 true
+
+-- !location_filter --
+0 8
+1 8
+10 8
+11 8
+12 8
+13 8
+14 8
+15 8
+2 8
+3 8
+4 8
+5 8
+6 8
+7 8
+8 8
+9 8
+
+-- !cell_grouping --
+0 578712552117108735 2
+1 583194161511923711 2
+10 623725832880947199 2
+11 628229432508297215 2
+12 632733032135666687 2
+13 637236631763037119 2
+14 641740231390407607 2
+15 646243831017778096 2
+2 587697211383480319 2
+3 592200673571897343 2
+4 596704238839529471 2
+5 601207835245674495 2
+6 605711434470391807 2
+7 610215034013876223 2
+8 614718633626566655 2
+9 619222233253675007 2
+
+-- !constant_with_columns --
+0 619056821840379903 619056821840379903 578536630256664575
+1 617440756014514175 619143828350959615 578536630256664575
+120 619056821840379903 619056821840379903 646078419604526808
+121 617440756014514175 619143828350959615 646078419604526808
+122 617048546304851967 619222233253675007 646078419604526808
+123 621260594331254783 619222233253675007 646078419604526808
+124 619056821840379903 619222233253675007 646078419604526808
+125 619056821840379903 619222233253675007 646078419604526808
+126 618001544260616191 618952619549786111 646078419604526808
+127 620377570945269759 619095778567389183 646078419604526808
+2 617048546304851967 619222233253675007 578536630256664575
+3 621260594331254783 619222233253675007 578536630256664575
+4 619056821840379903 619222233253675007 578536630256664575
+5 619056821840379903 619222233253675007 578536630256664575
+6 618001544260616191 618952619549786111 578536630256664575
+7 620377570945269759 619095778567389183 578536630256664575
+
+-- !stored_arrays --
+1 2 6 [55.712902431, 37.795066168] [[55.712900225,
37.795058112], [55.712897135, 37.79506507], [55.712899341, 37.795073127],
[55.712904638, 37.795074225], [55.712907728, 37.795067267], [55.712905522,
37.79505921]]
+2 \N \N \N \N
+3 \N \N \N \N
+4 \N \N \N \N
+5 \N \N \N \N
+6 2 6 [48.758349724, 18.303044481] [[55.706768465,
5.523646549], [45.18424869, 2.026568965], [37.523712366, 13.244313476],
[39.643310548, 28.846868847], [49.779342868, 36.116723632], [58.40154487,
25.082722327]]
+7 2 6 [79.24239851, 38.023407008] [[68.929957882,
31.831280499], [69.39359649, 62.345344957], [76.16304283, 94.143090102],
[87.364695323, 145.558197691], [81.27137179, -34.75841798], [73.310223685,
0.325610352]]
+
+-- !exploded_boundary --
+1 55.712897135 37.79506507
+1 55.712899341 37.795073127
+1 55.712900225 37.795058112
+1 55.712904638 37.795074225
+1 55.712905522 37.79505921
+1 55.712907728 37.795067267
+6 37.523712366 13.244313476
+6 39.643310548 28.846868847
+6 45.18424869 2.026568965
+6 49.779342868 36.116723632
+6 55.706768465 5.523646549
+6 58.40154487 25.082722327
+7 68.929957882 31.831280499
+7 69.39359649 62.345344957
+7 73.310223685 0.325610352
+7 76.16304283 94.143090102
+7 81.27137179 -34.75841798
+7 87.364695323 145.558197691
+
+-- !array_composition --
+1 6 [2, 2, 2, 2, 2, 2] 644325524701193974
+2 \N \N \N
+3 \N \N \N
+4 \N \N \N
+5 \N \N \N
+6 6 [2, 2, 2, 2, 2, 2] 644542512295313408
+7 6 [2, 2, 2, 2, 2, 2] 644014746713980928
+
+-- !batch_nulls --
+0 5000 5000 5000 5000 10000 30000
+1 5000 5000 5000 5000 10000 30000
+2 5000 0 0 0 \N \N
+3 5000 0 0 0 \N \N
+4 5000 0 0 0 \N \N
+5 5000 0 0 0 \N \N
+6 5000 5000 5000 5000 10000 30000
+
+-- !batch_filter --
+15000
+
+-- !batch_cell_grouping --
+\N 20000
+576495936675512319 5000
+644325524701193974 5000
+648282192095284420 5000
+
diff --git
a/regression-test/suites/query_p0/sql_functions/spatial_functions/test_h3.groovy
b/regression-test/suites/query_p0/sql_functions/spatial_functions/test_h3.groovy
new file mode 100644
index 00000000000..e2cd487bb8b
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/spatial_functions/test_h3.groovy
@@ -0,0 +1,188 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+
+suite("test_h3") {
+ for (def foldOnBe in [false, true]) {
+ sql "set enable_fold_constant_by_be = ${foldOnBe}"
+ qt_fold_cell """select h3_lat_lng_to_cell(55.71290588,37.79506683,15),
+ h3_lat_lng_to_cell(cast('55.71290588' as double),
+ cast('37.79506683' as double),abs(-15))"""
+ qt_fold_coordinates """select
+ array_map(x -> round(x,9),h3_cell_to_lat_lng(644325524701193974)),
+ array_map(p -> array_map(x -> round(x,9),p),
+ h3_cell_to_boundary(644325524701193974))"""
+ qt_fold_nested """select h3_lat_lng_to_cell(
+ h3_cell_to_lat_lng(644325524701193974)[1],
+ h3_cell_to_lat_lng(644325524701193974)[2],15),
+ size(h3_cell_to_lat_lng(h3_lat_lng_to_cell(0,0,0))),
+ size(h3_cell_to_boundary(h3_lat_lng_to_cell(0,0,15)))"""
+ qt_fold_null """select h3_lat_lng_to_cell(cast(null as double),0,9),
+ h3_lat_lng_to_cell(0,cast(null as double),9),
+ h3_lat_lng_to_cell(0,0,cast(null as int)),
+ h3_cell_to_lat_lng(cast(null as bigint)),
+ h3_cell_to_boundary(cast(null as bigint)),
+ h3_cell_to_lat_lng(h3_lat_lng_to_cell(91,0,9))"""
+ qt_fold_invalid """select h3_lat_lng_to_cell(-91,0,9),
+ h3_lat_lng_to_cell(0,-181,9),h3_lat_lng_to_cell(0,0,16),
+ h3_cell_to_lat_lng(12345),h3_cell_to_boundary(-1),
+ h3_cell_to_boundary(1509016653156329206)"""
+ qt_fold_endpoints """select h3_lat_lng_to_cell(90,180,0),
+ h3_lat_lng_to_cell(-90,-180,15),h3_lat_lng_to_cell(0,180,15),
+ h3_lat_lng_to_cell(0,-180,15)"""
+ }
+ sql "set enable_fold_constant_by_be = false"
+ explain {
+ sql """select h3_lat_lng_to_cell(55.71290588,37.79506683,15),
+ h3_cell_to_lat_lng(644325524701193974),
+ h3_cell_to_boundary(644325524701193974)"""
+ contains "h3_lat_lng_to_cell(55.71290588, 37.79506683, 15)"
+ contains "h3_cell_to_lat_lng(644325524701193974)"
+ contains "h3_cell_to_boundary(644325524701193974)"
+ }
+ sql "set enable_fold_constant_by_be = true"
+ explain {
+ sql "select h3_lat_lng_to_cell(55.71290588,37.79506683,15)"
+ contains "644325524701193974"
+ notContains "h3_lat_lng_to_cell(55.71290588, 37.79506683, 15)"
+ }
+ sql "set enable_fold_constant_by_be = false"
+ qt_cell "select h3_lat_lng_to_cell(55.71290588, 37.79506683, 15)"
+ qt_center """select array_map(x -> round(x, 9),
+ h3_cell_to_lat_lng(644325524701193974))"""
+ qt_boundary """select array_map(p -> array_map(x -> round(x, 9), p),
+ h3_cell_to_boundary(644325524701193974))"""
+ qt_invalid """select h3_lat_lng_to_cell(91, 0, 9),
+ h3_lat_lng_to_cell(0, 181, 9), h3_lat_lng_to_cell(0, 0, -1),
+ h3_lat_lng_to_cell(0, 0, 16), h3_cell_to_lat_lng(0),
+ h3_cell_to_lat_lng(-1), h3_cell_to_boundary(12345)"""
+ qt_null """select h3_lat_lng_to_cell(null, 0, 9),
+ h3_lat_lng_to_cell(0, null, 9), h3_lat_lng_to_cell(0, 0, null),
+ h3_cell_to_lat_lng(null), h3_cell_to_boundary(null)"""
+ qt_non_cell """select h3_cell_to_lat_lng(1509016653156329206),
+ h3_cell_to_boundary(1509016653156329206),
+ h3_cell_to_lat_lng(2517822969687320233),
+ h3_cell_to_boundary(2517822969687320233),
+ h3_cell_to_lat_lng(9223372036854775807)"""
+ qt_pentagons """select size(h3_cell_to_boundary(576636674163867647)),
+ size(h3_cell_to_boundary(581109487465660415)),
+ array_map(p -> array_map(x -> round(x,9),p),
+ h3_cell_to_boundary(581109487465660415))"""
+ sql "drop table if exists test_h3_inputs"
+ sql """create table test_h3_inputs (
+ id int, lat double, lng double, res int, cell bigint
+ ) distributed by hash(id) buckets 1 properties("replication_num"="1")"""
+ sql """insert into test_h3_inputs values
+ (1,55.71290588,37.79506683,15,644325524701193974),
+ (2,91,0,9,0), (3,null,0,9,null), (4,0,null,9,-1),
+ (5,0,0,null,12345), (6,90,180,0,577023702256844799),
+ (7,-90,-180,15,576495936675512319)"""
+ order_qt_rows """select id, h3_lat_lng_to_cell(lat,lng,res),
+ array_map(x -> round(x, 9), h3_cell_to_lat_lng(cell)),
+ array_map(p -> array_map(x -> round(x, 9),p),h3_cell_to_boundary(cell))
+ from test_h3_inputs"""
+ order_qt_mixed """select id, h3_lat_lng_to_cell(lat,0,9),
+ h3_lat_lng_to_cell(0,lng,res) from test_h3_inputs"""
+ order_qt_array_access """select id,size(h3_cell_to_lat_lng(cell)),
+
round(h3_cell_to_lat_lng(cell)[1],9),round(h3_cell_to_lat_lng(cell)[2],9),
+ h3_cell_to_lat_lng(cell)[3],
+ round(h3_cell_to_boundary(cell)[1][1],9),
+ round(h3_cell_to_boundary(cell)[1][2],9)
+ from test_h3_inputs"""
+ order_qt_null_columns """select id,
+ h3_lat_lng_to_cell(if(id>0,null,lat),lng,res),
+ h3_cell_to_lat_lng(if(id>0,null,cell)),
+ h3_cell_to_boundary(if(id>0,null,cell)) from test_h3_inputs"""
+ order_qt_empty """select h3_lat_lng_to_cell(lat,lng,res),
+ h3_cell_to_lat_lng(cell),h3_cell_to_boundary(cell)
+ from test_h3_inputs where id < 0"""
+ qt_roundtrip """select h3_lat_lng_to_cell(
+ h3_cell_to_lat_lng(644325524701193974)[1],
+ h3_cell_to_lat_lng(644325524701193974)[2],15)"""
+ qt_many_rows """select count(*), sum(h3_lat_lng_to_cell(0,0,cast(number %
16 as int)) is null),
+ sum(size(h3_cell_to_boundary(if(number % 2=0,644325524701193974,0)))),
+ sum(size(h3_cell_to_lat_lng(if(number % 2=0,644325524701193974,null))))
+ from numbers('number'='10000')"""
+
+ sql "drop table if exists test_h3_locations"
+ sql """create table test_h3_locations (
+ id int not null, lat double not null, lng double not null, res int not
null,
+ cell bigint not null
+ ) distributed by hash(id) buckets 3 properties("replication_num"="1")"""
+ sql """insert into test_h3_locations
+ select cast(number as int),
+ case number % 8 when 0 then 0 when 1 then 55.71290588
+ when 2 then 90 when 3 then -90 when 4 then 0
+ when 5 then 0 when 6 then 37.7749 else -33.8688 end,
+ case number % 8 when 0 then 0 when 1 then 37.79506683
+ when 2 then 180 when 3 then -180 when 4 then 180
+ when 5 then -180 when 6 then -122.4194 else 151.2093 end,
+ cast(floor(number / 8) as int), 0
+ from numbers('number'='128')"""
+ sql """insert overwrite table test_h3_locations
+ select id,lat,lng,res,h3_lat_lng_to_cell(lat,lng,res) from
test_h3_locations"""
+ order_qt_all_resolutions """select id,res,cell,
+ size(h3_cell_to_lat_lng(cell)),size(h3_cell_to_boundary(cell)),
+ h3_lat_lng_to_cell(h3_cell_to_lat_lng(cell)[1],
+ h3_cell_to_lat_lng(cell)[2],res)=cell as center_roundtrip
+ from test_h3_locations"""
+ order_qt_location_filter """select res,count(*)
+ from test_h3_locations where h3_cell_to_lat_lng(cell) is not null
+ and size(h3_cell_to_boundary(cell)) between 5 and 10 group by
res"""
+ order_qt_cell_grouping """select
res,h3_lat_lng_to_cell(lat,lng,res),count(*)
+ from test_h3_locations where id % 8 in (4,5)
+ group by res,h3_lat_lng_to_cell(lat,lng,res)"""
+ order_qt_constant_with_columns """select id,
+ h3_lat_lng_to_cell(lat,0,9),h3_lat_lng_to_cell(0,lng,9),
+ h3_lat_lng_to_cell(0,0,res) from test_h3_locations where res in
(0,15)"""
+
+ sql "drop table if exists test_h3_arrays"
+ sql """create table test_h3_arrays (
+ id int, center array<double>, boundary array<array<double>>
+ ) distributed by hash(id) buckets 1 properties("replication_num"="1")"""
+ sql """insert into test_h3_arrays select id,h3_cell_to_lat_lng(cell),
+ h3_cell_to_boundary(cell) from test_h3_inputs"""
+ order_qt_stored_arrays """select id,size(center),size(boundary),
+ array_map(x -> round(x,9),center),
+ array_map(p -> array_map(x -> round(x,9),p),boundary)
+ from test_h3_arrays"""
+ order_qt_exploded_boundary """select
id,round(vertex[1],9),round(vertex[2],9)
+ from test_h3_arrays lateral view explode(boundary) points as vertex"""
+ order_qt_array_composition """select id,
+ size(array_filter(p -> p[1] >= 0,h3_cell_to_boundary(cell))),
+ array_map(p -> size(p),h3_cell_to_boundary(cell)),
+ h3_lat_lng_to_cell(center[1],center[2],15)
+ from test_h3_inputs join test_h3_arrays using(id)"""
+
+ sql "drop table if exists test_h3_batches"
+ sql """create table test_h3_batches (
+ id int, lat double, lng double, res int, cell bigint
+ ) distributed by hash(id) buckets 3 properties("replication_num"="1")"""
+ sql """insert into test_h3_batches
+ select cast(n.number*7+i.id as int),i.lat,i.lng,i.res,i.cell
+ from test_h3_inputs i cross join numbers('number'='5000') n"""
+ order_qt_batch_nulls """select id % 7,count(*),
+ count(h3_lat_lng_to_cell(lat,lng,res)),
+ count(h3_cell_to_lat_lng(cell)),count(h3_cell_to_boundary(cell)),
+
sum(size(h3_cell_to_lat_lng(cell))),sum(size(h3_cell_to_boundary(cell)))
+ from test_h3_batches group by id % 7"""
+ qt_batch_filter """select count(*) from test_h3_batches
+ where h3_lat_lng_to_cell(lat,lng,res) is not null
+ and h3_cell_to_boundary(cell) is not null"""
+ order_qt_batch_cell_grouping """select
h3_lat_lng_to_cell(lat,lng,res),count(*)
+ from test_h3_batches group by h3_lat_lng_to_cell(lat,lng,res)"""
+}
diff --git a/run-be-ut.sh b/run-be-ut.sh
index 201dfcc68c9..2485582ca6e 100755
--- a/run-be-ut.sh
+++ b/run-be-ut.sh
@@ -256,12 +256,14 @@ update_submodule() {
# Keep this list in sync with build.sh: be/CMakeLists.txt adds
storage/index/ann
# unconditionally, and its cmake-protect target add_subdirectory()s both faiss
and openblas,
-# so the configure step fails outright when either is missing.
+# so the configure step fails outright when either is missing. The H3 wrapper
also
+# requires its submodule to be initialized before configuring BE.
update_submodule "contrib/datasketches-cpp" "datasketches-cpp"
"https://github.com/apache/datasketches-cpp/archive/refs/heads/master.tar.gz"
update_submodule "contrib/apache-orc" "apache-orc"
"https://github.com/apache/doris-thirdparty/archive/refs/heads/orc.tar.gz"
update_submodule "contrib/clucene" "clucene"
"https://github.com/apache/doris-thirdparty/archive/refs/heads/clucene.tar.gz"
update_submodule "contrib/openblas" "openblas"
"https://github.com/apache/doris-thirdparty/archive/refs/heads/openblas.tar.gz"
update_submodule "contrib/faiss" "faiss"
"https://github.com/apache/doris-thirdparty/archive/refs/heads/faiss.tar.gz"
+update_submodule "contrib/h3" "h3"
"https://github.com/uber/h3/archive/refs/tags/v4.5.0.tar.gz"
if [[ "_${DENABLE_CLANG_COVERAGE}" == "_ON" ]]; then
echo "export DORIS_TOOLCHAIN=clang" >>custom_env.sh
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]