OIiveirra commented on code in PR #69032:
URL: https://github.com/apache/doris/pull/69032#discussion_r4323001027
##########
be/src/exprs/function/geo/functions_geo.cpp:
##########
@@ -32,18 +44,302 @@
#include "core/column/column_array.h"
#include "core/column/column_execute_util.h"
#include "core/column/column_nullable.h"
+#include "core/column/column_varbinary.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 "core/data_type/data_type_spatial.h"
#include "core/data_type/data_type_string.h"
+#include "core/data_type/data_type_varbinary.h"
#include "core/data_type/define_primitive_type.h"
#include "core/string_ref.h"
#include "exprs/function/geo/geo_common.h"
#include "exprs/function/geo/geo_types.h"
+#include "exprs/function/geo/wkb_parse.h"
#include "exprs/function/simple_function_factory.h"
+#include "exprs/function/string_hex_util.h"
namespace doris {
+static bool is_spatial_type(const DataTypePtr& type) {
+ const auto primitive_type = remove_nullable(type)->get_primitive_type();
+ return primitive_type == TYPE_GEOMETRY || primitive_type == TYPE_GEOGRAPHY;
+}
+
+static Status validate_geography_semantics(const DataTypePtr& type, const
char* function_name) {
+ const auto& nested_type = remove_nullable(type);
+ if (!is_spatial_type(nested_type)) {
+ return Status::OK();
+ }
+
+ const auto* spatial_type = dynamic_cast<const
DataTypeSpatial*>(nested_type.get());
+ DCHECK(spatial_type != nullptr);
+ if (spatial_type->get_primitive_type() == TYPE_GEOGRAPHY &&
+ spatial_type->crs() == "OGC:CRS84" && spatial_type->algorithm() ==
"spherical") {
+ return Status::OK();
+ }
+ return Status::NotSupported(
+ "Function {} requires GEOGRAPHY(OGC:CRS84, spherical) for spatial
inputs",
+ function_name);
+}
+
+static std::unique_ptr<GeoShape> decode_geo_shape(StringRef value, const
DataTypePtr& type) {
+ if (!is_spatial_type(type)) {
+ return GeoShape::from_encoded(value.data, value.size);
+ }
+
+ GeoParseStatus status;
+ auto shape = GeoShape::from_wkb_bytes(value.data, value.size, status);
+ return status == GeoParseStatus::GEO_PARSE_OK ? std::move(shape) : nullptr;
+}
+
+static bool any_geo_argument_is_null(const Block& block, const ColumnNumbers&
arguments,
+ size_t row) {
+ return std::ranges::any_of(arguments, [&](auto argument) {
+ return block.get_by_position(argument).column->is_null_at(row);
+ });
+}
+
+static Status decode_calculation_shape(StringRef value, const DataTypePtr&
type,
+ std::unique_ptr<GeoShape>* shape,
+ const char* function_name) {
+ *shape = decode_geo_shape(value, type);
+ // POLYGON EMPTY decodes successfully, but distance helpers retain
infinity/max sentinels.
+ // Reject it for typed calculations just like empty points/lines, leaving
raw WKB untouched.
+ if (is_spatial_type(type) &&
+ (*shape == nullptr ||
+ ((*shape)->type() == GeoShapeType::GEO_SHAPE_POLYGON &&
+ assert_cast<const GeoPolygon&>(**shape).polygon()->num_loops() ==
0))) {
+ return Status::NotSupported("Function {} does not support this
Geography WKB shape",
+ function_name);
+ }
+ return Status::OK();
+}
+
+// Calculations use canonical positions; raw spatial accessors still return
the original WKB.
+static S2Point canonical_geography_point(const S2Point& point) {
+ const auto coordinate = S2LatLng(point);
+ const double latitude = coordinate.lat().radians();
+ // All longitudes at a pole and +/-180 at the date line denote the same
position.
+ if (std::abs(latitude) == M_PI_2) {
+ return {0, 0, latitude > 0 ? 1.0 : -1.0};
+ }
+ if (coordinate.lng().radians() == M_PI) {
+ // Flip only sin(longitude): rebuilding latitude can change its last
bit.
+ return {point.x(), -point.y(), point.z()};
+ }
+ return point;
+}
+
+static void geography_shape_index(const GeoShape& shape, MutableS2ShapeIndex*
index) {
+ switch (shape.type()) {
+ case GeoShapeType::GEO_SHAPE_LINE_STRING: {
+ const auto* line = assert_cast<const GeoLine&>(shape).polyline();
+ std::vector<S2Point> vertices;
+ for (int i = 0; i < line->num_vertices(); ++i) {
+ vertices.push_back(canonical_geography_point(line->vertex(i)));
+ }
+ index->Add(std::make_unique<S2LaxPolylineShape>(vertices));
+ break;
+ }
+ case GeoShapeType::GEO_SHAPE_POLYGON: {
+ const auto* polygon = assert_cast<const GeoPolygon&>(shape).polygon();
+ std::vector<S2LaxPolygonShape::Loop> loops;
+ for (int i = 0; i < polygon->num_loops(); ++i) {
+ auto& vertices = loops.emplace_back();
+ const auto* loop = polygon->loop(i);
+ for (int j = 0; j < loop->num_vertices(); ++j) {
+
vertices.push_back(canonical_geography_point(loop->oriented_vertex(j)));
+ }
+ }
+ index->Add(std::make_unique<S2LaxPolygonShape>(loops));
+ break;
+ }
+ default:
+ // decode_calculation_shape admits only nonempty
Point/LineString/Polygon WKB.
+ throw Exception(ErrorCode::INTERNAL_ERROR, "Unexpected Geography
calculation shape");
+ }
+}
+
+static bool geography_point_relation(std::string_view name, const GeoShape&
lhs,
+ const GeoShape& rhs) {
+ const auto& point_shape = lhs.type() == GeoShapeType::GEO_SHAPE_POINT ?
lhs : rhs;
+ const auto& other = lhs.type() == GeoShapeType::GEO_SHAPE_POINT ? rhs :
lhs;
+ const auto point =
+ canonical_geography_point(*assert_cast<const
GeoPoint&>(point_shape).point());
+ bool interior = false;
+ bool boundary = false;
+ if (other.type() == GeoShapeType::GEO_SHAPE_POINT) {
+ interior = point == canonical_geography_point(*assert_cast<const
GeoPoint&>(other).point());
+ } else if (other.type() == GeoShapeType::GEO_SHAPE_LINE_STRING) {
+ const auto* line = assert_cast<const GeoLine&>(other).polyline();
+ bool member = false;
+ // S2 Boolean operations symbolically move points off edge interiors.
Use its
+ // unperturbed distance predicate on every real geodesic edge instead.
+ for (int i = 0; i + 1 < line->num_vertices(); ++i) {
+ member = member ||
+ s2pred::CompareEdgeDistance(point,
canonical_geography_point(line->vertex(i)),
+
canonical_geography_point(line->vertex(i + 1)),
+ S1ChordAngle::Zero()) <= 0;
Review Comment:
Fixed in cd5c70d13db. Line membership and polygon boundary checks now allow
coordinate-conversion roundoff. Tests cover meridian points, endpoints and
nearby off-edge points in both operand orders; the spatial ASAN UTs pass
(119/119).
##########
be/src/core/data_type/data_type_spatial.cpp:
##########
@@ -0,0 +1,219 @@
+// 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 "core/data_type/data_type_spatial.h"
+
+#include <algorithm>
+#include <cctype>
+#include <cstring>
+#include <string_view>
+
+#include "agent/be_exec_version_manager.h"
+#include "common/exception.h"
+#include "core/assert_cast.h"
+#include "core/column/column_const.h"
+#include "core/data_type/data_type.h"
+#include "core/data_type_serde/data_type_varbinary_serde.h"
+#include "core/field.h"
+#include "core/string_view.h"
+#include "exprs/function/geo/wkb_parse.h"
+#include "gen_cpp/data.pb.h"
+
+namespace doris {
+
+namespace {
+
+std::string normalize_spatial_crs(std::string crs) {
+ // Imported ogc:crs84 has default semantics; leave custom CRS/property
references unchanged.
+ constexpr std::string_view default_crs = "OGC:CRS84";
+ if (std::ranges::equal(crs, default_crs, [](unsigned char left, unsigned
char right) {
+ return std::tolower(left) == std::tolower(right);
+ })) {
+ return std::string(default_crs);
+ }
+ return crs;
+}
+
+std::string normalize_spatial_algorithm(PrimitiveType type, std::string
algorithm) {
+ if (type == TYPE_GEOGRAPHY && algorithm.empty()) {
+ return "spherical";
+ }
+ std::ranges::transform(algorithm, algorithm.begin(),
+ [](unsigned char c) { return
static_cast<char>(std::tolower(c)); });
+ return algorithm;
+}
+
+class SpatialParquetSerDe final : public DataTypeVarbinarySerDe {
+public:
+ SpatialParquetSerDe(int nesting_level, bool geographic)
+ : DataTypeVarbinarySerDe(nesting_level), _geographic(geographic) {}
+
+ Status read_column_from_parquet(IColumn& column, ParquetDecodeSource&
source,
+ const ParquetDecodeContext& context,
size_t num_values,
+ ParquetMaterializationState& state) const
override {
+ const auto start = column.size();
+
RETURN_IF_ERROR(DataTypeVarbinarySerDe::read_column_from_parquet(column,
source, context,
+
num_values, state));
+ // Validate materialized values, not unselected dictionary entries.
NULL runs
+ // are appended by the reader outside the leaf SerDe.
+ for (size_t row = start; row < column.size(); ++row) {
+ const auto value = column.get_data_at(row);
+ RETURN_IF_ERROR(
+ WkbParse::validate_spatial_wkb(value.data, value.size,
_geographic, row));
+ }
+ return Status::OK();
+ }
+
+private:
+ const bool _geographic;
+};
+
+} // namespace
+
+DataTypeSpatial::DataTypeSpatial(PrimitiveType primitive_type, std::string crs,
+ std::string algorithm)
+ : _primitive_type(primitive_type),
+ _crs(normalize_spatial_crs(std::move(crs))),
+ _algorithm(normalize_spatial_algorithm(primitive_type,
std::move(algorithm))) {
+ DCHECK(primitive_type == TYPE_GEOMETRY || primitive_type ==
TYPE_GEOGRAPHY);
+ DCHECK(!_crs.empty());
+ DCHECK(primitive_type == TYPE_GEOMETRY || !_algorithm.empty());
+ DCHECK(primitive_type == TYPE_GEOGRAPHY || _algorithm.empty());
+}
+
+void DataTypeSpatial::to_pb_column_meta(PColumnMeta* col_meta) const {
+ IDataType::to_pb_column_meta(col_meta);
+ col_meta->set_spatial_crs(_crs);
+ if (_primitive_type == TYPE_GEOGRAPHY) {
+ col_meta->set_spatial_algorithm(_algorithm);
+ }
+}
+
+DataTypeSerDeSPtr DataTypeSpatial::get_serde(int nesting_level) const {
+ return std::make_shared<SpatialParquetSerDe>(nesting_level,
requires_geographic_coordinates());
Review Comment:
Fixed in cd5c70d13db. Nested spatial COLLECT_LIST inputs are rejected before
coercion; scalar spatial inputs remain supported. Added both-kind and two-phase
analysis coverage, including an imported nested column. FE UTs (3/3) and the
Iceberg suite (1/1) pass.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]