diff --git a/be/src/geo/geo_types.cpp b/be/src/geo/geo_types.cpp index 6c6f612ca1b93b..46d0798a0a5bd6 100644 --- a/be/src/geo/geo_types.cpp +++ b/be/src/geo/geo_types.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ // IWYU pragma: no_include #include #include +#include #include #include #include @@ -1630,5 +1632,361 @@ std::string GeoShape::as_binary(GeoShape* rhs) { return res; } +// Helper function to compute distance from a point to a line segment +double distance_point_to_segment(const S2Point& point, const S2Point& line_start, + const S2Point& line_end) { + S1Angle angle = S2::GetDistance(point, line_start, line_end); + return S2Earth::ToMeters(angle); +} + +// Helper function to compute distance from a point to a polyline +double distance_point_to_polyline(const S2Point& point, const S2Polyline* polyline) { + if (polyline->num_vertices() == 0) { + return std::numeric_limits::max(); + } + if (polyline->num_vertices() == 1) { + return S2Earth::GetDistanceMeters(point, polyline->vertex(0)); + } + + S1Angle min_angle = S1Angle::Infinity(); + for (int i = 0; i < polyline->num_vertices() - 1; ++i) { + const S2Point& p1 = polyline->vertex(i); + const S2Point& p2 = polyline->vertex(i + 1); + + S1Angle dist = S2::GetDistance(point, p1, p2); + if (dist < min_angle) { + min_angle = dist; + } + } + + return S2Earth::ToMeters(min_angle); +} + +// Helper function to compute distance from a point to a polygon +double distance_point_to_polygon(const S2Point& point, const S2Polygon* polygon) { + // Check if point is inside polygon + if (polygon->Contains(point)) { + return 0.0; + } + + // Find minimum distance to polygon boundary + S1Angle min_angle = S1Angle::Infinity(); + + for (int i = 0; i < polygon->num_loops(); ++i) { + const S2Loop* loop = polygon->loop(i); + + for (int j = 0; j < loop->num_vertices(); ++j) { + const S2Point& p1 = loop->vertex(j); + const S2Point& p2 = loop->vertex((j + 1) % loop->num_vertices()); + + S1Angle dist = S2::GetDistance(point, p1, p2); + if (dist < min_angle) { + min_angle = dist; + } + } + } + + return S2Earth::ToMeters(min_angle); +} + +double GeoPoint::Length() const { + // Point has no length + return 0.0; +} + +double GeoLine::Length() const { + // GeoLine is always valid with at least 2 vertices (guaranteed by constructor) + double total_length = 0.0; + for (int i = 0; i < _polyline->num_vertices() - 1; ++i) { + const S2Point& p1 = _polyline->vertex(i); + const S2Point& p2 = _polyline->vertex(i + 1); + + S2LatLng lat_lng1(p1); + S2LatLng lat_lng2(p2); + + // Calculate distance in meters using S2Earth + double distance = S2Earth::GetDistanceMeters(lat_lng1, lat_lng2); + total_length += distance; + } + + return total_length; +} + +double GeoPolygon::Length() const { + // GeoPolygon is always valid with at least one loop (guaranteed by constructor) + double perimeter = 0.0; + + for (int loop_idx = 0; loop_idx < _polygon->num_loops(); ++loop_idx) { + const S2Loop* loop = _polygon->loop(loop_idx); + for (int i = 0; i < loop->num_vertices(); ++i) { + const S2Point& p1 = loop->vertex(i); + const S2Point& p2 = loop->vertex((i + 1) % loop->num_vertices()); + + S2LatLng lat_lng1(p1); + S2LatLng lat_lng2(p2); + + // Calculate distance in meters using S2Earth + double distance = S2Earth::GetDistanceMeters(lat_lng1, lat_lng2); + perimeter += distance; + } + } + + return perimeter; +} + +double GeoMultiPolygon::Length() const { + double total_length = 0.0; + + // Calculate the perimeter of all polygons + for (const auto& polygon : _polygons) { + total_length += polygon->Length(); + } + + return total_length; +} + +double GeoCircle::Length() const { + // GeoCircle is always valid (guaranteed by constructor) + // Get the radius in meters + double radius_meters = S2Earth::ToMeters(_cap->radius()); + + // Calculate circumference: 2 * pi * r + return 2.0 * M_PI * radius_meters; +} + +double GeoPoint::Distance(const GeoShape* rhs) const { + // rhs is guaranteed to be valid by StDistance (functions_geo.cpp) + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = static_cast(rhs); + S2LatLng this_ll = S2LatLng(*_point); + S2LatLng other_ll = S2LatLng(*point->point()); + return S2Earth::GetDistanceMeters(this_ll, other_ll); + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = static_cast(rhs); + return distance_point_to_polyline(*_point, line->polyline()); + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* polygon = static_cast(rhs); + return distance_point_to_polygon(*_point, polygon->polygon()); + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = static_cast(rhs); + S2LatLng this_ll = S2LatLng(*_point); + S2LatLng center_ll = S2LatLng(circle->circle()->center()); + double dist_to_center = S2Earth::GetDistanceMeters(this_ll, center_ll); + double circle_radius = S2Earth::ToMeters(circle->circle()->radius()); + + // Distance from point to circle is distance to center minus radius + return std::max(0.0, dist_to_center - circle_radius); + } + case GEO_SHAPE_MULTI_POLYGON: { + return rhs->Distance(this); // Delegate to MultiPolygon's implementation + } + default: + return -1.0; + } +} + +double GeoLine::Distance(const GeoShape* rhs) const { + // rhs is guaranteed to be valid by StDistance (functions_geo.cpp) + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = static_cast(rhs); + return distance_point_to_polyline(*point->point(), _polyline.get()); + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* other_line = static_cast(rhs); + if (_polyline->Intersects(*other_line->polyline())) { + return 0.0; + } + double min_distance = std::numeric_limits::max(); + + // Check distance from each vertex of this line to other line + for (int i = 0; i < _polyline->num_vertices(); ++i) { + double dist = distance_point_to_polyline(_polyline->vertex(i), other_line->polyline()); + min_distance = std::min(min_distance, dist); + } + + // Check distance from each vertex of other line to this line + for (int i = 0; i < other_line->polyline()->num_vertices(); ++i) { + double dist = + distance_point_to_polyline(other_line->polyline()->vertex(i), _polyline.get()); + min_distance = std::min(min_distance, dist); + } + + return min_distance; + } + case GEO_SHAPE_POLYGON: { + return rhs->Distance(this); // Delegate to Polygon's implementation + } + case GEO_SHAPE_CIRCLE: { + return rhs->Distance(this); // Delegate to Circle's implementation + } + case GEO_SHAPE_MULTI_POLYGON: { + return rhs->Distance(this); // Delegate to MultiPolygon's implementation + } + default: + return -1.0; + } +} + +double GeoPolygon::Distance(const GeoShape* rhs) const { + // rhs is guaranteed to be valid by StDistance (functions_geo.cpp) + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = static_cast(rhs); + return distance_point_to_polygon(*point->point(), _polygon.get()); + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = static_cast(rhs); + if (_polygon->Intersects(*line->polyline())) { + return 0.0; + } + double min_distance = std::numeric_limits::max(); + + // Check distance from each vertex of line to polygon + for (int i = 0; i < line->polyline()->num_vertices(); ++i) { + double dist = distance_point_to_polygon(line->polyline()->vertex(i), _polygon.get()); + min_distance = std::min(min_distance, dist); + } + + // Check distance from each polygon vertex to line + for (int i = 0; i < _polygon->num_loops(); ++i) { + const S2Loop* loop = _polygon->loop(i); + for (int j = 0; j < loop->num_vertices(); ++j) { + double dist = distance_point_to_polyline(loop->vertex(j), line->polyline()); + min_distance = std::min(min_distance, dist); + } + } + + return min_distance; + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* other = static_cast(rhs); + if (_polygon->Intersects(*other->polygon()) || + polygon_touch_polygon(_polygon.get(), other->polygon())) { + return 0.0; + } + double min_distance = std::numeric_limits::max(); + + // Check distance from each vertex of this polygon to other polygon + for (int i = 0; i < _polygon->num_loops(); ++i) { + const S2Loop* loop = _polygon->loop(i); + for (int j = 0; j < loop->num_vertices(); ++j) { + double dist = distance_point_to_polygon(loop->vertex(j), other->polygon()); + min_distance = std::min(min_distance, dist); + } + } + + // Check distance from each vertex of other polygon to this polygon + for (int i = 0; i < other->polygon()->num_loops(); ++i) { + const S2Loop* loop = other->polygon()->loop(i); + for (int j = 0; j < loop->num_vertices(); ++j) { + double dist = distance_point_to_polygon(loop->vertex(j), _polygon.get()); + min_distance = std::min(min_distance, dist); + } + } + + return min_distance; + } + case GEO_SHAPE_CIRCLE: { + return rhs->Distance(this); // Delegate to Circle's implementation + } + case GEO_SHAPE_MULTI_POLYGON: { + return rhs->Distance(this); // Delegate to MultiPolygon's implementation + } + default: + return -1.0; + } +} + +double GeoMultiPolygon::Distance(const GeoShape* rhs) const { + // rhs is guaranteed to be valid by StDistance (functions_geo.cpp) + double min_distance = std::numeric_limits::max(); + + // Calculate minimum distance from any polygon to the other shape + for (const auto& polygon : _polygons) { + double dist = polygon->Distance(rhs); + if (dist >= 0) { + min_distance = std::min(min_distance, dist); + } + } + + return (min_distance == std::numeric_limits::max()) ? -1.0 : min_distance; +} + +double GeoCircle::Distance(const GeoShape* rhs) const { + // Both rhs and self are guaranteed to be valid by StDistance (functions_geo.cpp) + double circle_radius = S2Earth::ToMeters(_cap->radius()); + S2LatLng center_ll = S2LatLng(_cap->center()); + + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = static_cast(rhs); + S2LatLng point_ll = S2LatLng(*point->point()); + double dist_to_center = S2Earth::GetDistanceMeters(center_ll, point_ll); + return std::max(0.0, dist_to_center - circle_radius); + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = static_cast(rhs); + double min_distance = std::numeric_limits::max(); + + // Find minimum distance from circle center to line + for (int i = 0; i < line->polyline()->num_vertices() - 1; ++i) { + double dist = distance_point_to_segment(_cap->center(), line->polyline()->vertex(i), + line->polyline()->vertex(i + 1)); + min_distance = std::min(min_distance, dist); + } + + if (min_distance <= circle_radius + TOLERANCE) { + return 0.0; + } + return std::max(0.0, min_distance - circle_radius); + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* polygon = static_cast(rhs); + + // If center is inside polygon, distance is 0 + if (polygon->polygon()->Contains(_cap->center())) { + return 0.0; + } + + // Find minimum distance from circle center to polygon boundary + double min_distance = std::numeric_limits::max(); + + for (int i = 0; i < polygon->polygon()->num_loops(); ++i) { + const S2Loop* loop = polygon->polygon()->loop(i); + for (int j = 0; j < loop->num_vertices(); ++j) { + double dist = + distance_point_to_segment(_cap->center(), loop->vertex(j), + loop->vertex((j + 1) % loop->num_vertices())); + min_distance = std::min(min_distance, dist); + } + } + + if (min_distance <= circle_radius + TOLERANCE) { + return 0.0; + } + return std::max(0.0, min_distance - circle_radius); + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* other = static_cast(rhs); + double other_radius = S2Earth::ToMeters(other->circle()->radius()); + S2LatLng other_center_ll = S2LatLng(other->circle()->center()); + double dist_centers = S2Earth::GetDistanceMeters(center_ll, other_center_ll); + + // Distance between circles is distance between centers minus sum of radii + return std::max(0.0, dist_centers - circle_radius - other_radius); + } + case GEO_SHAPE_MULTI_POLYGON: { + return rhs->Distance(this); // Delegate to MultiPolygon's implementation + } + default: + return -1.0; + } +} + #include "common/compile_check_avoid_end.h" } // namespace doris diff --git a/be/src/geo/geo_types.h b/be/src/geo/geo_types.h index 9b2543b522755e..09eeb44c742202 100644 --- a/be/src/geo/geo_types.h +++ b/be/src/geo/geo_types.h @@ -70,6 +70,12 @@ class GeoShape { virtual bool touches(const GeoShape* rhs) const { return false; } + virtual std::string GeometryType() const = 0; + + virtual double Length() const { return 0.0; } + + virtual double Distance(const GeoShape* rhs) const { return -1.0; } + virtual std::string to_string() const { return ""; } static std::string as_binary(GeoShape* rhs); @@ -108,9 +114,14 @@ class GeoPoint : public GeoShape { static bool ComputeAngle(GeoPoint* p1, GeoPoint* p2, GeoPoint* p3, double* angle); static bool ComputeAzimuth(GeoPoint* p1, GeoPoint* p2, double* angle); + std::string GeometryType() const override { return "ST_POINT"; } std::string to_string() const override; std::string as_wkt() const override; + double Length() const override; + + double Distance(const GeoShape* rhs) const override; + double x() const; double y() const; @@ -140,8 +151,13 @@ class GeoLine : public GeoShape { GeoShapeType type() const override { return GEO_SHAPE_LINE_STRING; } const S2Polyline* polyline() const { return _polyline.get(); } + std::string GeometryType() const override { return "ST_LINESTRING"; } std::string as_wkt() const override; + double Length() const override; + + double Distance(const GeoShape* rhs) const override; + int numPoint() const; const S2Point* getPoint(int i) const; @@ -174,11 +190,13 @@ class GeoPolygon : public GeoShape { bool polygon_touch_point(const S2Polygon* polygon, const S2Point* point) const; bool polygon_touch_polygon(const S2Polygon* polygon1, const S2Polygon* polygon2) const; - + std::string GeometryType() const override { return "ST_POLYGON"; } std::string as_wkt() const override; int numLoops() const; double getArea() const; + double Length() const override; + double Distance(const GeoShape* rhs) const override; S2Loop* getLoop(int i) const; protected: @@ -207,9 +225,12 @@ class GeoMultiPolygon : public GeoShape { bool disjoint(const GeoShape* rhs) const override; bool touches(const GeoShape* rhs) const override; bool contains(const GeoShape* rhs) const override; + std::string GeometryType() const override { return "ST_MULTIPOLYGON"; } std::string as_wkt() const override; double getArea() const; + double Length() const override; + double Distance(const GeoShape* rhs) const override; protected: void encode(std::string* buf) override; @@ -236,9 +257,12 @@ class GeoCircle : public GeoShape { bool disjoint(const GeoShape* rhs) const override; bool touches(const GeoShape* rhs) const override; bool contains(const GeoShape* rhs) const override; + std::string GeometryType() const override { return "ST_CIRCLE"; } std::string as_wkt() const override; double getArea() const; + double Length() const override; + double Distance(const GeoShape* rhs) const override; protected: void encode(std::string* buf) override; diff --git a/be/src/vec/functions/functions_geo.cpp b/be/src/vec/functions/functions_geo.cpp index 01b98957795ea9..31010b8f80f55f 100644 --- a/be/src/vec/functions/functions_geo.cpp +++ b/be/src/vec/functions/functions_geo.cpp @@ -858,6 +858,189 @@ struct StAsBinary { } }; +struct StLength { + static constexpr auto NAME = "st_length"; + static const size_t NUM_ARGS = 1; + using Type = DataTypeFloat64; + static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { + DCHECK_EQ(arguments.size(), 1); + auto return_type = block.get_data_type(result); + + auto col = block.get_by_position(arguments[0]).column->convert_to_full_column_if_const(); + const auto size = col->size(); + auto res = ColumnFloat64::create(); + res->reserve(size); + auto null_map = ColumnUInt8::create(size, 0); + auto& null_map_data = null_map->get_data(); + + std::unique_ptr shape; + for (int row = 0; row < size; ++row) { + auto shape_value = col->get_data_at(row); + shape = GeoShape::from_encoded(shape_value.data, shape_value.size); + if (!shape) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + + double length = shape->Length(); + res->insert_value(length); + } + + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + return Status::OK(); + } +}; + +struct StGeometryType { + static constexpr auto NAME = "st_geometrytype"; + static const size_t NUM_ARGS = 1; + using Type = DataTypeString; + static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { + DCHECK_EQ(arguments.size(), 1); + auto return_type = block.get_data_type(result); + + auto col = block.get_by_position(arguments[0]).column->convert_to_full_column_if_const(); + const auto size = col->size(); + auto res = ColumnString::create(); + auto null_map = ColumnUInt8::create(size, 0); + auto& null_map_data = null_map->get_data(); + + std::unique_ptr shape; + for (int row = 0; row < size; ++row) { + auto shape_value = col->get_data_at(row); + shape = GeoShape::from_encoded(shape_value.data, shape_value.size); + if (!shape) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + + auto geo_type = shape->GeometryType(); + res->insert_data(geo_type.data(), geo_type.size()); + } + + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + return Status::OK(); + } +}; + +struct StDistance { + static constexpr auto NAME = "st_distance"; + static const size_t NUM_ARGS = 2; + using Type = DataTypeFloat64; + + static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { + DCHECK_EQ(arguments.size(), 2); + auto return_type = block.get_data_type(result); + const auto& [left_column, left_const] = + unpack_if_const(block.get_by_position(arguments[0]).column); + const auto& [right_column, right_const] = + unpack_if_const(block.get_by_position(arguments[1]).column); + + const auto size = std::max(left_column->size(), right_column->size()); + + auto res = ColumnFloat64::create(); + res->reserve(size); + auto null_map = ColumnUInt8::create(size, 0); + auto& null_map_data = null_map->get_data(); + + if (left_const) { + const_vector(left_column, right_column, res, null_map_data, size); + } else if (right_const) { + vector_const(left_column, right_column, res, null_map_data, size); + } else { + vector_vector(left_column, right_column, res, null_map_data, size); + } + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + return Status::OK(); + } + + static bool decode_shape(const StringRef& value, std::unique_ptr& shape) { + shape = GeoShape::from_encoded(value.data, value.size); + return static_cast(shape); + } + + static void loop_do(StringRef& lhs_value, StringRef& rhs_value, + std::vector>& shapes, + ColumnFloat64::MutablePtr& res, NullMap& null_map, int row) { + StringRef* strs[2] = {&lhs_value, &rhs_value}; + for (int i = 0; i < 2; ++i) { + if (!decode_shape(*strs[i], shapes[i])) { + null_map[row] = 1; + res->insert_default(); + return; + } + } + res->insert_value(shapes[0]->Distance(shapes[1].get())); + } + + static void const_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnFloat64::MutablePtr& res, NullMap& null_map, const size_t size) { + auto lhs_value = left_column->get_data_at(0); + std::unique_ptr lhs_shape; + if (!decode_shape(lhs_value, lhs_shape)) { + for (int row = 0; row < size; ++row) { + null_map[row] = 1; + res->insert_default(); + } + return; + } + + std::unique_ptr rhs_shape; + for (int row = 0; row < size; ++row) { + auto rhs_value = right_column->get_data_at(row); + if (!decode_shape(rhs_value, rhs_shape)) { + null_map[row] = 1; + res->insert_default(); + continue; + } + res->insert_value(lhs_shape->Distance(rhs_shape.get())); + } + } + + static void vector_const(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnFloat64::MutablePtr& res, NullMap& null_map, const size_t size) { + auto rhs_value = right_column->get_data_at(0); + std::unique_ptr rhs_shape; + if (!decode_shape(rhs_value, rhs_shape)) { + for (int row = 0; row < size; ++row) { + null_map[row] = 1; + res->insert_default(); + } + return; + } + + std::unique_ptr lhs_shape; + for (int row = 0; row < size; ++row) { + auto lhs_value = left_column->get_data_at(row); + if (!decode_shape(lhs_value, lhs_shape)) { + null_map[row] = 1; + res->insert_default(); + continue; + } + res->insert_value(lhs_shape->Distance(rhs_shape.get())); + } + } + + static void vector_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnFloat64::MutablePtr& res, NullMap& null_map, + const size_t size) { + const auto* left_string = assert_cast(left_column.get()); + const auto* right_string = assert_cast(right_column.get()); + + std::vector> shapes(2); + for (int row = 0; row < size; ++row) { + auto lhs_value = left_string->get_data_at(row); + auto rhs_value = right_string->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, res, null_map, row); + } + } +}; + void register_function_geo(SimpleFunctionFactory& factory) { factory.register_function>(); factory.register_function>>(); @@ -885,6 +1068,9 @@ void register_function_geo(SimpleFunctionFactory& factory) { factory.register_function>>(); factory.register_function>>(); factory.register_function>(); + factory.register_function>(); + factory.register_function>(); + factory.register_function>(); } } // namespace doris::vectorized diff --git a/be/test/geo/geo_types_test.cpp b/be/test/geo/geo_types_test.cpp index cebb4f9296a677..ef5f9083c6f422 100644 --- a/be/test/geo/geo_types_test.cpp +++ b/be/test/geo/geo_types_test.cpp @@ -1356,6 +1356,540 @@ TEST_F(GeoTypesTest, circle_touches) { } } +TEST_F(GeoTypesTest, test_geometry_type) { + GeoParseStatus status; + + // Test GeoPoint + { + GeoPoint point; + point.from_coord(116.123, 63.546); + EXPECT_STREQ("ST_POINT", point.GeometryType().c_str()); + } + + // Test GeoLineString + { + const char* wkt = "LINESTRING (30 10, 10 30, 40 40)"; + auto line = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line.get()); + EXPECT_STREQ("ST_LINESTRING", line->GeometryType().c_str()); + } + + // Test GeoPolygon + { + const char* wkt = "POLYGON ((10 10, 50 10, 50 50, 10 50, 10 10))"; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + EXPECT_STREQ("ST_POLYGON", polygon->GeometryType().c_str()); + } + + // Test GeoMultiPolygon + { + const char* wkt = "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)))"; + auto multi_polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, multi_polygon.get()); + EXPECT_STREQ("ST_MULTIPOLYGON", multi_polygon->GeometryType().c_str()); + } + + // Test GeoCircle + { + GeoCircle circle; + auto res = circle.init(110.123, 64, 1000); + EXPECT_EQ(GEO_PARSE_OK, res); + EXPECT_STREQ("ST_CIRCLE", circle.GeometryType().c_str()); + } +} + +TEST_F(GeoTypesTest, test_length) { + GeoParseStatus status; + + // Test GeoPoint - length should be 0 + { + GeoPoint point; + point.from_coord(116.123, 63.546); + EXPECT_DOUBLE_EQ(0.0, point.Length()); + } + + // Test GeoLineString - calculate length + { + const char* wkt = "LINESTRING (0 0, 1 0, 1 1)"; + auto line = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line.get()); + double length = line->Length(); + EXPECT_GT(length, 100000.0); + // Line should have two segments: (0,0)-(1,0) and (1,0)-(1,1) + // Expected total distance is approximately 2 degrees in Earth distance + EXPECT_LT(length, 300000.0); // Less than 300km + } + + // Test GeoPolygon - calculate perimeter + { + const char* wkt = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + double perimeter = polygon->Length(); + EXPECT_GT(perimeter, 400000.0); + // Polygon is 1x1 degree square, perimeter should be roughly 4 times one degree + EXPECT_LT(perimeter, 500000.0); // Less than 500km + } + + // Test GeoPolygon with a hole - perimeter should include inner loop + { + const char* outer_wkt = "POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))"; + const char* hole_wkt = + "POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0), (0.5 0.5, 1.5 0.5, 1.5 1.5, 0.5 1.5, " + "0.5 0.5))"; + auto outer_polygon = GeoShape::from_wkt(outer_wkt, strlen(outer_wkt), status); + auto hole_polygon = GeoShape::from_wkt(hole_wkt, strlen(hole_wkt), status); + EXPECT_NE(nullptr, outer_polygon.get()); + EXPECT_NE(nullptr, hole_polygon.get()); + + double outer_perimeter = outer_polygon->Length(); + double hole_perimeter = hole_polygon->Length(); + EXPECT_GT(hole_perimeter, outer_perimeter); + EXPECT_LT(hole_perimeter, outer_perimeter * 2.0); + } + + // Test GeoMultiPolygon - should have non-zero length + { + const char* wkt = "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))"; + auto multi_polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, multi_polygon.get()); + double length = multi_polygon->Length(); + EXPECT_GT(length, 400000.0); + EXPECT_LT(length, 500000.0); // Less than 500km + } + + // Test GeoCircle - length should be circumference (2 * pi * radius in meters) + { + GeoCircle circle; + auto res = circle.init(0, 0, 1000); // 1000 meters radius + EXPECT_EQ(GEO_PARSE_OK, res); + double circumference = circle.Length(); + EXPECT_GT(circumference, 0.0); + // Expected circumference: 2 * pi * 1000 ≈ 6283 meters + EXPECT_GT(circumference, 6200.0); + EXPECT_LT(circumference, 6300.0); + } +} + +TEST_F(GeoTypesTest, test_distance_point) { + GeoParseStatus status; + + GeoPoint point1; + point1.from_coord(0, 0); + + // ========================== + // GeoPoint vs GeoPoint + // ========================== + { + GeoPoint point2; + point2.from_coord(0, 0); + double dist = point1.Distance(&point2); + EXPECT_DOUBLE_EQ(0.0, dist); + } + { + GeoPoint point2; + point2.from_coord(1, 0); + double dist = point1.Distance(&point2); + EXPECT_GT(dist, 0.0); + // Distance should be roughly 111km for 1 degree latitude + EXPECT_GT(dist, 100000.0); + EXPECT_LT(dist, 120000.0); + } + + // ========================== + // GeoPoint vs GeoLineString + // ========================== + { + const char* wkt = "LINESTRING (0 0, 1 0, 1 1)"; + auto line = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line.get()); + double dist = point1.Distance(line.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Point is on the line at (0,0) + } + { + const char* wkt = "LINESTRING (5 5, 6 6)"; + auto line = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line.get()); + double dist = point1.Distance(line.get()); + EXPECT_GT(dist, 0.0); + // Distance from (0,0) to a line at (5,5)-(6,6) + EXPECT_GT(dist, 700000.0); // More than 700km + } + + // ========================== + // GeoPoint vs GeoPolygon + // ========================== + { + const char* wkt = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + double dist = point1.Distance(polygon.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Point is on the polygon boundary at (0,0) + } + { + const char* wkt = "POLYGON ((5 5, 6 5, 6 6, 5 6, 5 5))"; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + double dist = point1.Distance(polygon.get()); + EXPECT_GT(dist, 0.0); + // Distance from (0,0) to polygon at (5,5) + EXPECT_GT(dist, 700000.0); // More than 700km + } + + // ========================== + // GeoPoint vs GeoMultiPolygon + // ========================== + { + const char* wkt = "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))"; + auto multi_polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, multi_polygon.get()); + double dist = point1.Distance(multi_polygon.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Point is on the multipolygon boundary + } + + // ========================== + // GeoPoint vs GeoCircle + // ========================== + { + GeoCircle circle; + circle.init(0, 0, 10000); // 10km radius + double dist = point1.Distance(&circle); + EXPECT_DOUBLE_EQ(0.0, dist); // Point is at the center of circle + } + { + GeoCircle circle; + circle.init(5, 0, 10000); // 10km radius at (5,0) + double dist = point1.Distance(&circle); + EXPECT_GT(dist, 0.0); + // Point (0,0) is outside circle at (5,0) + EXPECT_LT(dist, 600000.0); // Should be less than 600km from center minus radius + } +} + +TEST_F(GeoTypesTest, test_distance_linestring) { + GeoParseStatus status; + + const char* base_line = "LINESTRING (0 0, 1 0, 1 1)"; + auto line1 = GeoShape::from_wkt(base_line, strlen(base_line), status); + EXPECT_NE(nullptr, line1.get()); + + // ========================== + // GeoLineString vs GeoPoint + // ========================== + { + GeoPoint point; + point.from_coord(0, 0); + double dist = line1->Distance(&point); + EXPECT_DOUBLE_EQ(0.0, dist); // Point is on the line + } + + // ========================== + // GeoLineString vs GeoLineString + // ========================== + { + const char* wkt = "LINESTRING (0 0, 1 0)"; + auto line2 = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line2.get()); + double dist = line1->Distance(line2.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Lines intersect + } + { + const char* wkt = "LINESTRING (-1 0.5, 2 0.5)"; + auto line2 = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line2.get()); + double dist = line1->Distance(line2.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Lines cross the vertical segment at x=1 + } + { + const char* wkt = "LINESTRING (5 5, 6 5)"; + auto line2 = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line2.get()); + double dist = line1->Distance(line2.get()); + EXPECT_GT(dist, 0.0); + // Lines are separate + EXPECT_GT(dist, 600000.0); + } + + // ========================== + // GeoLineString vs GeoPolygon + // ========================== + { + const char* wkt = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + double dist = line1->Distance(polygon.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Line is on/inside polygon + } + + // ========================== + // GeoLineString vs GeoMultiPolygon + // ========================== + { + const char* wkt = "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))"; + auto multi_polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, multi_polygon.get()); + double dist = line1->Distance(multi_polygon.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Line is on/inside multipolygon + } + + // ========================== + // GeoLineString vs GeoCircle + // ========================== + { + GeoCircle circle; + circle.init(0.5, 0.5, 100000); // 100km radius + double dist = line1->Distance(&circle); + EXPECT_DOUBLE_EQ(0.0, dist); // Line is within/touches circle + } +} + +TEST_F(GeoTypesTest, test_distance_polygon) { + GeoParseStatus status; + + const char* base_polygon = "POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))"; + auto polygon1 = GeoShape::from_wkt(base_polygon, strlen(base_polygon), status); + EXPECT_NE(nullptr, polygon1.get()); + + // ========================== + // GeoPolygon vs GeoPoint + // ========================== + { + GeoPoint point; + point.from_coord(0, 0); + double dist = polygon1->Distance(&point); + EXPECT_DOUBLE_EQ(0.0, dist); // Point is on polygon boundary + } + { + GeoPoint point; + point.from_coord(5, 5); + double dist = polygon1->Distance(&point); + EXPECT_GT(dist, 0.0); + // Point is outside polygon + EXPECT_GT(dist, 300000.0); + } + + // ========================== + // GeoPolygon vs GeoLineString + // ========================== + { + const char* wkt = "LINESTRING (0 0, 1 1)"; + auto line = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line.get()); + double dist = polygon1->Distance(line.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Line is within polygon + } + + // ========================== + // GeoPolygon vs GeoPolygon + // ========================== + { + const char* wkt = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"; + auto polygon2 = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon2.get()); + double dist = polygon1->Distance(polygon2.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Polygons overlap + } + { + const char* wkt = "POLYGON ((2 0, 4 0, 4 2, 2 2, 2 0))"; + auto polygon2 = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon2.get()); + double dist = polygon1->Distance(polygon2.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Polygons touch at the boundary + } + { + const char* wkt = "POLYGON ((5 5, 6 5, 6 6, 5 6, 5 5))"; + auto polygon2 = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon2.get()); + double dist = polygon1->Distance(polygon2.get()); + EXPECT_GT(dist, 0.0); + // Polygons are separate + EXPECT_GT(dist, 300000.0); + } + + // ========================== + // GeoPolygon vs GeoMultiPolygon + // ========================== + { + const char* wkt = "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))"; + auto multi_polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, multi_polygon.get()); + double dist = polygon1->Distance(multi_polygon.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // MultiPolygon overlaps with Polygon + } + + // ========================== + // GeoPolygon vs GeoCircle + // ========================== + { + GeoCircle circle; + circle.init(1, 1, 100000); // 100km radius at (1,1) + double dist = polygon1->Distance(&circle); + EXPECT_DOUBLE_EQ(0.0, dist); // Circle overlaps with polygon + } +} + +TEST_F(GeoTypesTest, test_distance_multipolygon) { + GeoParseStatus status; + + const char* base_multipolygon = + "MULTIPOLYGON (" + "((0 0, 2 0, 2 2, 0 2, 0 0))," + "((5 5, 7 5, 7 7, 5 7, 5 5))" + ")"; + auto multipolygon1 = GeoShape::from_wkt(base_multipolygon, strlen(base_multipolygon), status); + EXPECT_NE(nullptr, multipolygon1.get()); + + // ========================== + // GeoMultiPolygon vs GeoPoint + // ========================== + { + GeoPoint point; + point.from_coord(0, 0); + double dist = multipolygon1->Distance(&point); + EXPECT_DOUBLE_EQ(0.0, dist); // Point is on multipolygon boundary + } + { + GeoPoint point; + point.from_coord(10, 10); + double dist = multipolygon1->Distance(&point); + EXPECT_GT(dist, 0.0); + // Point is outside all polygons, distance to nearest polygon at (5,5)-(7,7) is ~469km + EXPECT_GT(dist, 400000.0); + EXPECT_LT(dist, 500000.0); + } + + // ========================== + // GeoMultiPolygon vs GeoLineString + // ========================== + { + const char* wkt = "LINESTRING (0 0, 1 1)"; + auto line = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line.get()); + double dist = multipolygon1->Distance(line.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Line is within first polygon + } + + // ========================== + // GeoMultiPolygon vs GeoPolygon + // ========================== + { + const char* wkt = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + double dist = multipolygon1->Distance(polygon.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Polygon overlaps with first multipolygon + } + { + const char* wkt = "POLYGON ((10 10, 12 10, 12 12, 10 12, 10 10))"; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + double dist = multipolygon1->Distance(polygon.get()); + EXPECT_GT(dist, 0.0); + // Polygon is separate from multipolygon, distance to nearest polygon at (5,5)-(7,7) is ~469km + EXPECT_GT(dist, 400000.0); + EXPECT_LT(dist, 500000.0); + } + + // ========================== + // GeoMultiPolygon vs GeoMultiPolygon + // ========================== + { + const char* wkt = "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))"; + auto multipolygon2 = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, multipolygon2.get()); + double dist = multipolygon1->Distance(multipolygon2.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Overlapping multipolygons + } + + // ========================== + // GeoMultiPolygon vs GeoCircle + // ========================== + { + GeoCircle circle; + circle.init(1, 1, 100000); // 100km radius + double dist = multipolygon1->Distance(&circle); + EXPECT_DOUBLE_EQ(0.0, dist); // Circle overlaps with first polygon + } +} + +TEST_F(GeoTypesTest, test_distance_circle) { + GeoParseStatus status; + + GeoCircle circle1; + circle1.init(0, 0, 10000); // 10km radius + + // ========================== + // GeoCircle vs GeoPoint + // ========================== + { + GeoPoint point; + point.from_coord(0, 0); + double dist = circle1.Distance(&point); + EXPECT_DOUBLE_EQ(0.0, dist); // Point is at center of circle + } + { + GeoPoint point; + point.from_coord(1, 0); + double dist = circle1.Distance(&point); + EXPECT_GT(dist, 0.0); + // Point is outside circle + EXPECT_LT(dist, 200000.0); // Less than 200km + } + + // ========================== + // GeoCircle vs GeoLineString + // ========================== + { + const char* wkt = "LINESTRING (0 0, 0 1)"; + auto line = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line.get()); + double dist = circle1.Distance(line.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Line is within circle + } + + // ========================== + // GeoCircle vs GeoPolygon + // ========================== + { + const char* wkt = "POLYGON ((0 0, 0.1 0, 0.1 0.1, 0 0.1, 0 0))"; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + double dist = circle1.Distance(polygon.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // Polygon is within circle + } + + // ========================== + // GeoCircle vs GeoMultiPolygon + // ========================== + { + const char* wkt = "MULTIPOLYGON (((0 0, 0.1 0, 0.1 0.1, 0 0.1, 0 0)))"; + auto multi_polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, multi_polygon.get()); + double dist = circle1.Distance(multi_polygon.get()); + EXPECT_DOUBLE_EQ(0.0, dist); // MultiPolygon is within circle + } + + // ========================== + // GeoCircle vs GeoCircle + // ========================== + { + GeoCircle circle2; + circle2.init(0, 0, 5000); // 5km radius at same center + double dist = circle1.Distance(&circle2); + EXPECT_DOUBLE_EQ(0.0, dist); // Circles overlap + } + { + GeoCircle circle2; + circle2.init(10, 0, 5000); // 5km radius at (10,0) + double dist = circle1.Distance(&circle2); + EXPECT_GT(dist, 0.0); + // Circles are separate, distance between centers ~1110km minus radii (10+5km) = ~1095km + EXPECT_GT(dist, 1000000.0); + EXPECT_LT(dist, 1100000.0); + } +} + TEST_F(GeoTypesTest, polygon_contains) { GeoParseStatus status; const char* wkt = "POLYGON ((10 10, 50 10, 50 10, 50 50, 50 50, 10 50, 10 10))"; 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 19f806b964d6cb..87809fe7c1ebe8 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 @@ -461,12 +461,15 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.StCircle; import org.apache.doris.nereids.trees.expressions.functions.scalar.StContains; import org.apache.doris.nereids.trees.expressions.functions.scalar.StDisjoint; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StDistance; import org.apache.doris.nereids.trees.expressions.functions.scalar.StDistanceSphere; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeomFromWKB; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryFromWKB; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryType; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeomfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StIntersects; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StLength; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinefromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinestringfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPoint; @@ -1020,6 +1023,9 @@ public class BuiltinScalarFunctions implements FunctionHelper { scalar(StIntersects.class, "st_intersects"), scalar(StDisjoint.class, "st_disjoint"), scalar(StTouches.class, "st_touches"), + scalar(StLength.class, "st_length"), + scalar(StGeometryType.class, "st_geometrytype"), + scalar(StDistance.class, "st_distance"), scalar(StDistanceSphere.class, "st_distance_sphere"), scalar(StAngleSphere.class, "st_angle_sphere"), scalar(StAngle.class, "st_angle"), diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StDistance.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StDistance.java new file mode 100644 index 00000000000000..dbed7879d6b069 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StDistance.java @@ -0,0 +1,75 @@ +// 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.BinaryExpression; +import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.types.DoubleType; +import org.apache.doris.nereids.types.VarcharType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * ScalarFunction 'st_distance'. + */ +public class StDistance extends ScalarFunction + implements BinaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(DoubleType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT) + ); + + /** + * constructor with 2 arguments. + */ + public StDistance(Expression arg0, Expression arg1) { + super("st_distance", arg0, arg1); + } + + /** constructor for withChildren and reuse signature */ + private StDistance(ScalarFunctionParams functionParams) { + super(functionParams); + } + + /** + * withChildren. + */ + @Override + public StDistance withChildren(List children) { + Preconditions.checkArgument(children.size() == 2); + return new StDistance(getFunctionParams(children)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStDistance(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeometryType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeometryType.java new file mode 100644 index 00000000000000..882c07182328ec --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeometryType.java @@ -0,0 +1,74 @@ +// 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.VarcharType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * ScalarFunction 'st_geometrytype'. + */ +public class StGeometryType extends ScalarFunction + implements UnaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT).args(VarcharType.SYSTEM_DEFAULT) + ); + + /** + * constructor with 1 argument. + */ + public StGeometryType(Expression arg0) { + super("st_geometrytype", arg0); + } + + /** constructor for withChildren and reuse signature */ + private StGeometryType(ScalarFunctionParams functionParams) { + super(functionParams); + } + + /** + * withChildren. + */ + @Override + public StGeometryType withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new StGeometryType(getFunctionParams(children)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStGeometryType(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StLength.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StLength.java new file mode 100644 index 00000000000000..963de26cea7b61 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StLength.java @@ -0,0 +1,75 @@ +// 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.DoubleType; +import org.apache.doris.nereids.types.VarcharType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * ScalarFunction 'st_length'. + */ +public class StLength extends ScalarFunction + implements UnaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(DoubleType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT) + ); + + /** + * constructor with 1 argument. + */ + public StLength(Expression arg0) { + super("st_length", arg0); + } + + /** constructor for withChildren and reuse signature */ + private StLength(ScalarFunctionParams functionParams) { + super(functionParams); + } + + /** + * withChildren. + */ + @Override + public StLength withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new StLength(getFunctionParams(children)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStLength(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 f09bd61f8c36f3..a78deebdbaaeec 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 @@ -466,12 +466,15 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.StCircle; import org.apache.doris.nereids.trees.expressions.functions.scalar.StContains; import org.apache.doris.nereids.trees.expressions.functions.scalar.StDisjoint; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StDistance; import org.apache.doris.nereids.trees.expressions.functions.scalar.StDistanceSphere; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeomFromWKB; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryFromWKB; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryType; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeomfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StIntersects; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StLength; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinefromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinestringfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPoint; @@ -2217,6 +2220,18 @@ default R visitStTouches(StTouches stTouches, C context) { return visitScalarFunction(stTouches, context); } + default R visitStLength(StLength stLength, C context) { + return visitScalarFunction(stLength, context); + } + + default R visitStGeometryType(StGeometryType stGeometryType, C context) { + return visitScalarFunction(stGeometryType, context); + } + + default R visitStDistance(StDistance stDistance, C context) { + return visitScalarFunction(stDistance, context); + } + default R visitStDistanceSphere(StDistanceSphere stDistanceSphere, C context) { return visitScalarFunction(stDistanceSphere, context); } diff --git a/regression-test/data/nereids_p0/sql_functions/spatial_functions/test_gis_function.out b/regression-test/data/nereids_p0/sql_functions/spatial_functions/test_gis_function.out index f9ca847769b6b0..8dd039d4e82f1a 100644 --- a/regression-test/data/nereids_p0/sql_functions/spatial_functions/test_gis_function.out +++ b/regression-test/data/nereids_p0/sql_functions/spatial_functions/test_gis_function.out @@ -782,6 +782,204 @@ true -- !sql -- 45.0 +-- ST_Length -- +-- !sql -- +0.0 + +-- !sql -- +333568.3675501366 + +-- !sql -- +4430868.978662349 + +-- !sql -- +6.283185307179586 + +-- !sql -- +3991709.218883891 + +-- !sql -- +333585.3035324518 + +-- !sql -- +0.0 + +-- !sql -- +555812.8141039772 + +-- !sql -- +2221785.032905475 + +-- !sql -- +31.41592653589793 + +-- !sql -- +4430868.978662349 + +-- ST_GeometryType -- +-- !sql -- +ST_POINT + +-- !sql -- +ST_LINESTRING + +-- !sql -- +ST_POLYGON + +-- !sql -- +ST_CIRCLE + +-- !sql -- +ST_MULTIPOLYGON + +-- !sql -- +ST_LINESTRING + +-- ST_Distance -- +-- Point-Point +-- !sql -- +0.0 + +-- !sql -- +555812.8141039772 + +-- !sql -- +555812.8141039772 + +-- Point-LineString +-- !sql -- +0.0 + +-- !sql -- +555975.5058874196 + +-- !sql -- +0.0 + +-- !sql -- +555975.5058874196 + +-- Point-Polygon +-- !sql -- +0.0 + +-- !sql -- +776861.3769121666 + +-- !sql -- +0.0 + +-- !sql -- +776861.3769121666 + +-- Point-Circle +-- !sql -- +0.0 + +-- !sql -- +222389.2023549679 + +-- !sql -- +0.0 + +-- !sql -- +222389.2023549679 + +-- Point-MultiPolygon +-- !sql -- +0.0 + +-- !sql -- +311622.5144267479 + +-- !sql -- +0.0 + +-- LineString-LineString +-- !sql -- +0.0 + +-- !sql -- +555975.5058874197 + +-- !sql -- +555975.5058874197 + +-- LineString-Polygon +-- !sql -- +0.0 + +-- !sql -- +0.0 + +-- !sql -- +0.0 + +-- LineString-Circle +-- !sql -- +555974.5058874196 + +-- !sql -- +555974.5058874196 + +-- !sql -- +0.0 + +-- LineString-MultiPolygon +-- !sql -- +220225.0495313765 + +-- Polygon-Polygon +-- !sql -- +0.0 + +-- !sql -- +547523.7460346645 + +-- Polygon-Circle +-- !sql -- +0.0 + +-- !sql -- +0.0 + +-- !sql -- +1107675.460602543 + +-- !sql -- +11.15730564761618 + +-- Polygon-MultiPolygon +-- !sql -- +0.0 + +-- !sql -- +776861.3769121666 + +-- Circle-Circle +-- !sql -- +0.0 + +-- !sql -- +555973.5058874197 + +-- !sql -- +555973.5058874197 + +-- Circle-MultiPolygon +-- !sql -- +0.0 + +-- !sql -- +1544758.985700087 + +-- MultiPolygon-MultiPolygon +-- !sql -- +0.0 + +-- !sql -- +1559539.293243077 + -- ST_AsText -- -- !sql -- LINESTRING (1 1, 2 2) @@ -909,6 +1107,33 @@ LINESTRING (1 1, 2 2) -- !sql -- POLYGON ((114.104486 22.547119, 114.093758 22.547753, 114.096504 22.532057, 114.104229 22.539826, 114.106203 22.54268, 114.104486 22.547119)) +-- !sql -- +1 ST_POINT +2 ST_LINESTRING +3 ST_LINESTRING +4 ST_POLYGON +5 ST_POLYGON +6 ST_MULTIPOLYGON +7 \N + +-- !sql -- +1 0.0 +2 111195.10117748393 +3 111195.10117748393 +4 444763.46872762055 +5 2667259.843412653 +6 889323.7417861816 +7 \N + +-- !sql -- +1 0.0 +2 0.0 +3 0.0 +4 0.0 +5 939785.7843902707 +6 0.0 +7 \N + -- !sql_part_const_dis_sph -- 3335.853035325018 diff --git a/regression-test/suites/nereids_p0/sql_functions/spatial_functions/test_gis_function.groovy b/regression-test/suites/nereids_p0/sql_functions/spatial_functions/test_gis_function.groovy index b8dc671f2831b5..1c8b713413694f 100644 --- a/regression-test/suites/nereids_p0/sql_functions/spatial_functions/test_gis_function.groovy +++ b/regression-test/suites/nereids_p0/sql_functions/spatial_functions/test_gis_function.groovy @@ -323,6 +323,88 @@ suite("test_gis_function") { qt_sql "SELECT ST_ANGLE_SPHERE(116.35620117, 39.939093, 116.4274406433, 39.9020987219);" qt_sql "SELECT ST_ANGLE_SPHERE(0, 0, 45, 0);" + // ST_Length tests for all geometry types + qt_sql "SELECT ST_Length(ST_Point(0, 0));" + qt_sql "SELECT ST_Length(ST_LineFromText(\"LINESTRING (0 0, 1 0, 1 1, 0 1)\"));" + qt_sql "SELECT ST_Length(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"));" + qt_sql "SELECT ST_Length(ST_Circle(0, 0, 1));" + qt_sql "SELECT ST_Length(ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + qt_sql "SELECT ST_Length(ST_GeometryFromText(\"LINESTRING (0 0, 1 0, 2 0, 3 0)\"));" + qt_sql "SELECT ST_Length(ST_Point(24.7, 56.7));" + qt_sql "SELECT ST_Length(ST_LineFromText(\"LINESTRING (0 0, 3 4)\"));" + qt_sql "SELECT ST_Length(ST_Polygon(\"POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))\"));" + qt_sql "SELECT ST_Length(ST_Circle(10, 20, 5));" + qt_sql "SELECT ST_Length(ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)))\"));" + + // ST_GeometryType tests for all geometry types + qt_sql "SELECT ST_GeometryType(ST_Point(0, 0));" + qt_sql "SELECT ST_GeometryType(ST_LineFromText(\"LINESTRING (0 0, 1 0, 1 1, 0 1)\"));" + qt_sql "SELECT ST_GeometryType(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"));" + qt_sql "SELECT ST_GeometryType(ST_Circle(0, 0, 1));" + qt_sql "SELECT ST_GeometryType(ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + qt_sql "SELECT ST_GeometryType(ST_GeometryFromText(\"LINESTRING (0 0, 1 0, 2 0, 3 0)\"));" + + // ST_Distance tests for all geometry type combinations + qt_sql "SELECT ST_Distance(ST_Point(0, 0), ST_Point(0, 0));" + qt_sql "SELECT ST_Distance(ST_Point(0, 0), ST_Point(3, 4));" + qt_sql "SELECT ST_Distance(ST_Point(3, 4), ST_Point(0, 0));" + + qt_sql "SELECT ST_Distance(ST_Point(0, 0), ST_LineFromText(\"LINESTRING (0 0, 10 0)\"));" + qt_sql "SELECT ST_Distance(ST_Point(5, 5), ST_LineFromText(\"LINESTRING (0 0, 10 0)\"));" + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (0 0, 10 0)\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (0 0, 10 0)\"), ST_Point(5, 5));" + + qt_sql "SELECT ST_Distance(ST_Point(5, 5), ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"));" + qt_sql "SELECT ST_Distance(ST_Point(15, 15), ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"));" + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 5));" + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(15, 15));" + + qt_sql "SELECT ST_Distance(ST_Point(0, 0), ST_Circle(0, 0, 1));" + qt_sql "SELECT ST_Distance(ST_Point(2, 0), ST_Circle(0, 0, 1));" + qt_sql "SELECT ST_Distance(ST_Circle(0, 0, 1), ST_Point(0, 0));" + qt_sql "SELECT ST_Distance(ST_Circle(0, 0, 1), ST_Point(2, 0));" + + qt_sql "SELECT ST_Distance(ST_Point(2, 2), ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + qt_sql "SELECT ST_Distance(ST_Point(12, 12), ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + qt_sql "SELECT ST_Distance(ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"), ST_Point(2, 2));" + + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (0 0, 10 0)\"), ST_LineFromText(\"LINESTRING (0 0, 10 0)\"));" + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (0 0, 5 0)\"), ST_LineFromText(\"LINESTRING (10 0, 15 0)\"));" + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (10 0, 15 0)\"), ST_LineFromText(\"LINESTRING (0 0, 5 0)\"));" + + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (5 5, 5 15)\"), ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"));" + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (0 5, 10 5)\"), ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"));" + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (5 5, 5 15)\"));" + + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (0 0, 10 0)\"), ST_Circle(5, 5, 1));" + qt_sql "SELECT ST_Distance(ST_Circle(5, 5, 1), ST_LineFromText(\"LINESTRING (0 0, 10 0)\"));" + + // circle-line tangent cases (distance should be 0) + qt_sql "SELECT ST_Distance(ST_Circle(0, 0, 1), ST_LineFromText(\"LINESTRING (-1 0.00000899320363724538, 1 0.00000899320363724538)\"));" + + qt_sql "SELECT ST_Distance(ST_LineFromText(\"LINESTRING (12 2, 12 8)\"), ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"));" + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((15 0, 25 0, 25 10, 15 10, 15 0))\"));" + + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Circle(5, 5, 1));" + qt_sql "SELECT ST_Distance(ST_Circle(5, 5, 1), ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"));" + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Circle(20, 5, 1));" + qt_sql "SELECT ST_Distance(ST_Circle(0.001, 0, 50), ST_GeometryFromText(\"POLYGON ((-0.00045 -0.00045, 0.00045 -0.00045, 0.00045 0.00045, -0.00045 0.00045, -0.00045 -0.00045))\"));" + + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + qt_sql "SELECT ST_Distance(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_GeometryFromText(\"MULTIPOLYGON(((15 15, 15 20, 20 20, 20 15, 15 15)))\"));" + + qt_sql "SELECT ST_Distance(ST_Circle(0, 0, 1), ST_Circle(0, 0, 1));" + qt_sql "SELECT ST_Distance(ST_Circle(0, 0, 1), ST_Circle(5, 0, 1));" + qt_sql "SELECT ST_Distance(ST_Circle(5, 0, 1), ST_Circle(0, 0, 1));" + + qt_sql "SELECT ST_Distance(ST_Circle(2, 2, 1), ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + qt_sql "SELECT ST_Distance(ST_Circle(20, 20, 1), ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + + qt_sql "SELECT ST_Distance(ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"), ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)), ((6 6, 6 10, 10 10, 10 6, 6 6)))\"));" + qt_sql "SELECT ST_Distance(ST_GeometryFromText(\"MULTIPOLYGON(((0 0, 0 5, 5 5, 5 0, 0 0)))\"), ST_GeometryFromText(\"MULTIPOLYGON(((15 15, 15 20, 20 20, 20 15, 15 15)))\"));" + qt_sql "SELECT ST_AsText(ST_GeometryFromText(\"LINESTRING (1 1, 2 2)\"));" qt_sql "SELECT ST_AsText(ST_GeomFromText(\"LINESTRING (1 1, 2 2)\"));" @@ -376,6 +458,35 @@ suite("test_gis_function") { qt_sql "SELECT ST_AsText(ST_GeomFromWKB(ST_AsBinary(ST_GeometryFromText(\"LINESTRING (1 1, 2 2)\"))));" qt_sql "SELECT ST_AsText(ST_GeomFromWKB(ST_AsBinary(ST_Polygon(\"POLYGON ((114.104486 22.547119,114.093758 22.547753,114.096504 22.532057,114.104229 22.539826,114.106203 22.542680,114.104486 22.547119))\"))));" + // table-driven tests for ST_Length/ST_GeometryType/ST_Distance + sql "drop table if exists test_gis_table_cases" + sql """ + CREATE TABLE test_gis_table_cases ( + `id` int NOT NULL, + `wkt_a` varchar(512) NULL, + `wkt_b` varchar(512) NULL + ) ENGINE=OLAP + UNIQUE KEY(`id`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`) BUCKETS 4 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1" + ); + """ + sql """ + insert into test_gis_table_cases values + (1, 'POINT(0 0)', 'POINT(0 0)'), + (2, 'LINESTRING(0 0, 0 1)', 'POINT(0 0)'), + (3, 'LINESTRING(0 0, 1 0)', 'LINESTRING(0 0, 1 0)'), + (4, 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))', 'LINESTRING(0.2 0.2, 0.8 0.8)'), + (5, 'POLYGON((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 1 3, 3 3, 3 1, 1 1))', 'POINT(10 10)'), + (6, 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2 2)))', 'POINT(2.5 2.5)'), + (7, null, null); + """ + qt_sql "select id, ST_GeometryType(ST_GeomFromText(wkt_a)) from test_gis_table_cases order by id;" + qt_sql "select id, ST_Length(ST_GeomFromText(wkt_a)) from test_gis_table_cases order by id;" + qt_sql "select id, ST_Distance(ST_GeomFromText(wkt_a), ST_GeomFromText(wkt_b)) from test_gis_table_cases order by id;" + // test const sql "drop table if exists test_gis_const" sql """