From 78ad92b1a0f865f842f5b1c358c769e150308532 Mon Sep 17 00:00:00 2001 From: koi2000 Date: Wed, 5 Mar 2025 11:17:10 +0800 Subject: [PATCH 1/2] [feat][test](nereids)(vec) support the ST_Intersects, ST_Disjoint, ST_Touches function and test (#48203) --- be/src/geo/geo_types.cpp | 446 +++++++++++ be/src/geo/geo_types.h | 23 + be/src/vec/functions/functions_geo.cpp | 288 +++++++ be/test/geo/geo_types_test.cpp | 720 ++++++++++++++++++ .../doris/catalog/BuiltinScalarFunctions.java | 6 + .../functions/scalar/StDisjoint.java | 70 ++ .../functions/scalar/StIntersects.java | 70 ++ .../functions/scalar/StTouches.java | 70 ++ .../visitor/ScalarFunctionVisitor.java | 15 + gensrc/script/doris_builtins_functions.py | 5 +- .../spatial_functions/test_gis_function.out | 186 +++++ .../spatial_functions/test_gis_function.out | 186 +++++ .../test_gis_function.groovy | 81 ++ .../test_gis_function.groovy | 81 ++ 14 files changed, 2246 insertions(+), 1 deletion(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StDisjoint.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StIntersects.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StTouches.java diff --git a/be/src/geo/geo_types.cpp b/be/src/geo/geo_types.cpp index dc27595da3bfc6..0fe7fedbe92d56 100644 --- a/be/src/geo/geo_types.cpp +++ b/be/src/geo/geo_types.cpp @@ -20,8 +20,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -75,6 +78,19 @@ static inline GeoParseStatus to_s2point(double lng, double lat, S2Point* point) return GEO_PARSE_OK; } +static double compute_intersection_area(const S2Polygon* polygon1, const S2Polygon* polygon2) { + S2Polygon result; + S2BooleanOperation::Options options; + std::unique_ptr layer( + new s2builderutil::S2PolygonLayer(&result)); + S2BooleanOperation op(S2BooleanOperation::OpType::INTERSECTION, std::move(layer)); + S2Error error; + if (!op.Build(polygon1->index(), polygon2->index(), &error)) { + return 0.0; + } + return result.GetArea(); +} + static inline GeoParseStatus to_s2point(const GeoCoordinate& coord, S2Point* point) { return to_s2point(coord.x, coord.y, point); } @@ -299,6 +315,57 @@ const std::unique_ptr GeoPolygon::to_coords() const { return coordss; } +bool GeoPoint::intersects(const GeoShape* rhs) const { + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + // points and points are considered to intersect when they are equal + const GeoPoint* point = (const GeoPoint*)rhs; + return *_point == *point->point(); + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = (const GeoLine*)rhs; + return line->intersects(this); + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* polygon = (const GeoPolygon*)rhs; + return polygon->intersects(this); + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = (const GeoCircle*)rhs; + return circle->intersects(this); + } + default: + return false; + } +} + +bool GeoPoint::disjoint(const GeoShape* rhs) const { + return !intersects(rhs); +} + +bool GeoPoint::touches(const GeoShape* rhs) const { + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + // always returns false because the point has no boundaries + return false; + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = (const GeoLine*)rhs; + return line->touches(this); + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* polygon = (const GeoPolygon*)rhs; + return polygon->touches(this); + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = (const GeoCircle*)rhs; + return circle->touches(this); + } + default: + return false; + } +} + std::string GeoPoint::to_string() const { return as_wkt(); } @@ -403,6 +470,142 @@ GeoParseStatus GeoLine::from_coords(const GeoCoordinateList& list) { return to_s2polyline(list, &_polyline); } +bool GeoLine::intersects(const GeoShape* rhs) const { + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = (const GeoPoint*)rhs; + int next_vertex = 0; + // calculate the distance after finding the closest point to the line + S2Point closest_point = _polyline->Project(*point->point(), &next_vertex); + S1Angle distance = S1Angle(closest_point, *point->point()); + return distance.radians() < 1e-2; + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = (const GeoLine*)rhs; + if (!_polyline->Intersects(*line->polyline())) { + int next_vertex = 0; + // s2geometry may return an incorrect result when the two lines overlap at only one point + for (int i = 0; i < _polyline->num_vertices(); i++) { + const S2Point& p = _polyline->vertex(i); + S2Point closest_point = line->polyline()->Project(p, &next_vertex); + S1Angle distance = S1Angle(closest_point, p); + if (distance.radians() < 1e-2) { + return true; + } + } + + for (int i = 0; i < line->polyline()->num_vertices(); i++) { + const S2Point& p = line->polyline()->vertex(i); + S2Point closest_point = _polyline->Project(p, &next_vertex); + S1Angle distance = S1Angle(closest_point, p); + if (distance.radians() < 1e-2) { + return true; + } + } + return false; + } + return true; + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* polygon = (const GeoPolygon*)rhs; + return polygon->polygon()->Intersects(*_polyline); + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = (const GeoCircle*)rhs; + return circle->intersects(this); + } + default: + return false; + } +} + +bool GeoLine::disjoint(const GeoShape* rhs) const { + return !intersects(rhs); +} + +bool line_touches_line(const S2Polyline* line1, const S2Polyline* line2) { + std::vector cross_points; + // If two lines intersect, add their intersection points to the array + // When two lines intersect only at boundary points, s2geometry does not determine the intersection + // and needs to be added to the array manually. + for (int i = 1; i < line1->num_vertices(); ++i) { + S2EdgeCrosser crosser(&line1->vertex(i - 1), &line1->vertex(i), &line2->vertex(0)); + for (int j = 1; j < line2->num_vertices(); ++j) { + if (crosser.CrossingSign(&line2->vertex(j)) > 0) { + S2Point cross_point = S2::GetIntersection(line1->vertex(i - 1), line1->vertex(i), + line2->vertex(0), line2->vertex(j)); + cross_points.push_back(cross_point); + } else if (line1->vertex(i - 1) == line2->vertex(0) || + line1->vertex(i - 1) == line2->vertex(j)) { + cross_points.push_back(line1->vertex(i - 1)); + } else if (line1->vertex(i) == line2->vertex(0) || + line1->vertex(i) == line2->vertex(j)) { + cross_points.push_back(line1->vertex(i)); + } + } + } + // The intersection is judged to satisfy the touch condition + // if and only if the intersecting points lie on the boundary + for (const S2Point& point : cross_points) { + if (!(point == line1->vertex(0) || point == line1->vertex(line1->num_vertices() - 1) || + point == line2->vertex(0) || point == line2->vertex(line2->num_vertices() - 1))) { + return false; + } + } + // when no intersections are collected but intersects, the touches condition is satisfied + if (cross_points.empty()) { + int next_vertex = 0; + for (int i = 0; i < line1->num_vertices(); i++) { + const S2Point& p = line1->vertex(i); + S2Point closest_point = line2->Project(p, &next_vertex); + S1Angle distance = S1Angle(closest_point, p); + if (distance.radians() < 1e-2) { + return true; + } + } + + for (int i = 0; i < line2->num_vertices(); i++) { + const S2Point& p = line2->vertex(i); + S2Point closest_point = line1->Project(p, &next_vertex); + S1Angle distance = S1Angle(closest_point, p); + if (distance.radians() < 1e-2) { + return true; + } + } + return false; + } + return true; +} + +bool GeoLine::touches(const GeoShape* rhs) const { + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = (const GeoPoint*)rhs; + const S2Point& start = _polyline->vertex(0); + const S2Point& end = _polyline->vertex(_polyline->num_vertices() - 1); + // 1. Points do not have boundaries. when the point is on the start or end of the line return true + if (start == *point->point() || end == *point->point()) { + return true; + } + return false; + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* other = (const GeoLine*)rhs; + return line_touches_line(_polyline.get(), other->polyline()); + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* polygon = (const GeoPolygon*)rhs; + return polygon->touches(this); + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = (const GeoCircle*)rhs; + return circle->touches(this); + } + default: + return false; + } +} + void GeoLine::encode(std::string* buf) { Encoder encoder; _polyline->Encode(&encoder); @@ -476,6 +679,154 @@ std::string GeoPolygon::as_wkt() const { return ss.str(); } +bool GeoPolygon::intersects(const GeoShape* rhs) const { + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = (const GeoPoint*)rhs; + // 1. when polygon contain the point return true + if (_polygon->Contains(*point->point())) { + return true; + } + // 2. calculate the distance between the point and the closest point on the boundary + S2Point closest_point = _polygon->ProjectToBoundary(*point->point()); + S1Angle distance(closest_point, *point->point()); + return distance.radians() < 1e-2; + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = (const GeoLine*)rhs; + // + std::vector> intersect_lines = + _polygon->IntersectWithPolyline(*line->polyline()); + if (intersect_lines.empty()) { + int next; + // s2geometry may not return the correct result when the line is on the boundary. + 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& p = loop->vertex(j); + S2Point closest_point = line->polyline()->Project(p, &next); + S1Angle distance(closest_point, p); + if (distance.radians() < 1e-2) { + return true; + } + } + } + return false; + } + return true; + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* other = (const GeoPolygon*)rhs; + // When two polygons intersect only at the boundary, s2geometry may not return the correct result. + if (!_polygon->Intersects(*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) { + const S2Point& p = loop->vertex(j); + S2Point closest_point = other->polygon()->ProjectToBoundary(p); + S1Angle distance(closest_point, p); + if (distance.radians() < 1e-2) { + return true; + } + } + } + return false; + } + return true; + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = (const GeoCircle*)rhs; + return circle->intersects(this); + } + default: + return false; + } +} + +bool GeoPolygon::disjoint(const GeoShape* rhs) const { + return !intersects(rhs); +} + +bool GeoPolygon::touches(const GeoShape* rhs) const { + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = (const GeoPoint*)rhs; + S2Point closest_point = _polygon->ProjectToBoundary(*point->point()); + S1Angle distance(closest_point, *point->point()); + return distance.radians() < 1e-2; + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = (const GeoLine*)rhs; + std::vector> intersect_lines = + _polygon->IntersectWithPolyline(*line->polyline()); + + std::set polygon_points; + // 1. collect all points in the 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) { + const S2Point& p = loop->vertex(j); + polygon_points.insert(p); + } + } + // 2. check if the intersect line's points are on the polygon + for (auto& iline : intersect_lines) { + for (int i = 0; i < iline->num_vertices(); ++i) { + const S2Point& p = iline->vertex(i); + if (polygon_points.find(p) == polygon_points.end()) { + return false; + } + } + } + // 3. check if the line is on the boundary of the polygon + if (intersect_lines.empty()) { + int next; + for (const S2Point& p : polygon_points) { + S2Point closest_point = line->polyline()->Project(p, &next); + S1Angle distance(closest_point, p); + if (distance.radians() < 1e-2) { + return true; + } + } + } else { + for (auto& intersect_line : intersect_lines) { + if (!line_touches_line(intersect_line.get(), line->polyline())) { + return false; + } + } + return true; + } + return false; + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* other = (const GeoPolygon*)rhs; + const S2Polygon* polygon1 = _polygon.get(); + const S2Polygon* polygon2 = other->polygon(); + // when the two polygons do not have overlapping areas, then determine if the touch regulation is met. + if (compute_intersection_area(polygon1, polygon2) < 1e-4) { + for (int i = 0; i < polygon1->num_loops(); ++i) { + const S2Loop* loop = polygon1->loop(i); + for (int j = 0; j < loop->num_vertices(); ++j) { + const S2Point& p = loop->vertex(j); + S2Point closest_point = polygon2->ProjectToBoundary(p); + S1Angle distance(closest_point, p); + if (distance.radians() < 1e-2) { + return true; + } + } + } + } + return false; + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = (const GeoCircle*)rhs; + return circle->touches(this); + } + default: + return false; + } +} + bool GeoPolygon::contains(const GeoShape* rhs) const { switch (rhs->type()) { case GEO_SHAPE_POINT: { @@ -521,6 +872,101 @@ GeoParseStatus GeoCircle::init(double lng, double lat, double radius_meter) { return GEO_PARSE_OK; } +bool GeoCircle::intersects(const GeoShape* rhs) const { + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = (const GeoPoint*)rhs; + const S2Point& center = _cap->center(); + S1ChordAngle radius_angle = _cap->radius(); + S1Angle distance_angle = S1Angle(center, *point->point()); + // The radius unit of circle is initially in meters, + // which needs to be converted back to meters when comparing + double radius = S2Earth::RadiansToMeters(radius_angle.radians()); + return radius + 1e-4 >= distance_angle.degrees(); + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = (const GeoLine*)rhs; + const S2Point& center = _cap->center(); + S1ChordAngle radius_angle = _cap->radius(); + int next; + const S2Point& closest_point = line->polyline()->Project(center, &next); + S1Angle distance_angle(closest_point, center); + double radius = S2Earth::RadiansToMeters(radius_angle.radians()); + return radius + 1e-4 >= distance_angle.degrees(); + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* polygon = (const GeoPolygon*)rhs; + const S2Point& center = _cap->center(); + S1ChordAngle radius_angle = _cap->radius(); + + const S1Angle distance_angle = polygon->polygon()->GetDistance(center); + double radius = S2Earth::RadiansToMeters(radius_angle.radians()); + return radius + 1e-4 >= distance_angle.degrees(); + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = (const GeoCircle*)rhs; + S1Angle distance_angle = S1Angle(_cap->center(), circle->circle()->center()); + S1ChordAngle radius_angle = _cap->radius(); + S1ChordAngle other_radius_angle = circle->circle()->radius(); + + double radius1 = S2Earth::RadiansToMeters(radius_angle.radians()); + double radius2 = S2Earth::RadiansToMeters(other_radius_angle.radians()); + return radius1 + radius2 + 1e-4 >= distance_angle.degrees(); + } + default: + return false; + } +} + +bool GeoCircle::disjoint(const GeoShape* rhs) const { + return !intersects(rhs); +} + +bool GeoCircle::touches(const GeoShape* rhs) const { + switch (rhs->type()) { + case GEO_SHAPE_POINT: { + const GeoPoint* point = (const GeoPoint*)rhs; + const S2Point& center = _cap->center(); + S1ChordAngle radius_angle = _cap->radius(); + S1ChordAngle distance_angle = S1ChordAngle(center, *point->point()); + + double radius = S2Earth::RadiansToMeters(radius_angle.radians()); + return std::abs(radius - distance_angle.degrees()) < 1e-1; + } + case GEO_SHAPE_LINE_STRING: { + const GeoLine* line = (const GeoLine*)rhs; + const S2Point& center = _cap->center(); + S1ChordAngle radius_angle = _cap->radius(); + int next; + const S2Point& closest_point = line->polyline()->Project(center, &next); + S1ChordAngle distance_angle(closest_point, center); + double radius = S2Earth::RadiansToMeters(radius_angle.radians()); + return std::abs(radius - distance_angle.degrees()) < 1e-1; + } + case GEO_SHAPE_POLYGON: { + const GeoPolygon* polygon = (const GeoPolygon*)rhs; + const S2Point& center = _cap->center(); + S1ChordAngle radius_angle = _cap->radius(); + + const S1Angle distance_angle = polygon->polygon()->GetDistance(center); + double radius = S2Earth::RadiansToMeters(radius_angle.radians()); + return std::abs(radius - distance_angle.degrees()) < 1e-1; + } + case GEO_SHAPE_CIRCLE: { + const GeoCircle* circle = (const GeoCircle*)rhs; + S1ChordAngle distance_angle = S1ChordAngle(_cap->center(), circle->circle()->center()); + S1ChordAngle radius_angle = _cap->radius(); + S1ChordAngle other_radius_angle = circle->circle()->radius(); + + double radius1 = S2Earth::RadiansToMeters(radius_angle.radians()); + double radius2 = S2Earth::RadiansToMeters(other_radius_angle.radians()); + return std::abs(radius1 + radius2 - distance_angle.degrees()) < 1e-1; + } + default: + return false; + } +} + bool GeoCircle::contains(const GeoShape* rhs) const { switch (rhs->type()) { case GEO_SHAPE_POINT: { diff --git a/be/src/geo/geo_types.h b/be/src/geo/geo_types.h index aaeff3db58f228..6f3d3395546511 100644 --- a/be/src/geo/geo_types.h +++ b/be/src/geo/geo_types.h @@ -60,6 +60,13 @@ class GeoShape { virtual std::string as_wkt() const = 0; virtual bool contains(const GeoShape* rhs) const { return false; } + + virtual bool disjoint(const GeoShape* rhs) const { return false; } + + virtual bool intersects(const GeoShape* rhs) const { return false; } + + virtual bool touches(const GeoShape* rhs) const { return false; } + virtual std::string to_string() const { return ""; } static std::string as_binary(GeoShape* rhs); @@ -82,6 +89,10 @@ class GeoPoint : public GeoShape { GeoCoordinateList to_coords() const; + bool intersects(const GeoShape* rhs) const override; + bool disjoint(const GeoShape* rhs) const override; + bool touches(const GeoShape* rhs) const override; + GeoShapeType type() const override { return GEO_SHAPE_POINT; } const S2Point* point() const { return _point.get(); } @@ -119,6 +130,10 @@ class GeoLine : public GeoShape { GeoCoordinateList to_coords() const; + bool intersects(const GeoShape* rhs) const override; + bool disjoint(const GeoShape* rhs) const override; + bool touches(const GeoShape* rhs) const override; + GeoShapeType type() const override { return GEO_SHAPE_LINE_STRING; } const S2Polyline* polyline() const { return _polyline.get(); } @@ -148,6 +163,9 @@ class GeoPolygon : public GeoShape { GeoShapeType type() const override { return GEO_SHAPE_POLYGON; } const S2Polygon* polygon() const { return _polygon.get(); } + bool intersects(const GeoShape* rhs) const override; + bool disjoint(const GeoShape* rhs) const override; + bool touches(const GeoShape* rhs) const override; bool contains(const GeoShape* rhs) const override; std::string as_wkt() const override; @@ -174,6 +192,11 @@ class GeoCircle : public GeoShape { GeoShapeType type() const override { return GEO_SHAPE_CIRCLE; } + const S2Cap* circle() const { return _cap.get(); } + + bool intersects(const GeoShape* rhs) const override; + bool disjoint(const GeoShape* rhs) const override; + bool touches(const GeoShape* rhs) const override; bool contains(const GeoShape* rhs) const override; std::string as_wkt() const override; diff --git a/be/src/vec/functions/functions_geo.cpp b/be/src/vec/functions/functions_geo.cpp index 0a752af18fe04c..987a65f736053a 100644 --- a/be/src/vec/functions/functions_geo.cpp +++ b/be/src/vec/functions/functions_geo.cpp @@ -721,6 +721,291 @@ struct StContains { } }; // namespace doris::vectorized +struct StIntersects { + static constexpr auto NEED_CONTEXT = true; + static constexpr auto NAME = "st_intersects"; + static const size_t NUM_ARGS = 2; + using Type = DataTypeUInt8; + static Status execute(FunctionContext* context, 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 = ColumnUInt8::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 void loop_do(StringRef& lhs_value, StringRef& rhs_value, + std::vector>& shapes, int& i, + ColumnUInt8::MutablePtr& res, NullMap& null_map, int row) { + StringRef* strs[2] = {&lhs_value, &rhs_value}; + for (i = 0; i < 2; ++i) { + shapes[i] = + std::shared_ptr(GeoShape::from_encoded(strs[i]->data, strs[i]->size)); + if (shapes[i] == nullptr) { + null_map[row] = 1; + res->insert_default(); + break; + } + } + + if (i == 2) { + auto contains_value = shapes[0]->intersects(shapes[1].get()); + res->insert_data(const_cast((char*)&contains_value), 0); + } + } + + static void const_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + auto lhs_value = left_column->get_data_at(0); + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto rhs_value = right_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static void vector_const(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + auto rhs_value = right_column->get_data_at(0); + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto lhs_value = left_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static void vector_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto lhs_value = left_column->get_data_at(row); + auto rhs_value = right_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static Status open(FunctionContext* context, FunctionContext::FunctionStateScope scope) { + return Status::OK(); + } + + static Status close(FunctionContext* context, FunctionContext::FunctionStateScope scope) { + return Status::OK(); + } +}; // namespace doris::vectorized + +struct StDisjoint { + static constexpr auto NEED_CONTEXT = true; + static constexpr auto NAME = "st_disjoint"; + static const size_t NUM_ARGS = 2; + using Type = DataTypeUInt8; + static Status execute(FunctionContext* context, 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 = ColumnUInt8::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 void loop_do(StringRef& lhs_value, StringRef& rhs_value, + std::vector>& shapes, int& i, + ColumnUInt8::MutablePtr& res, NullMap& null_map, int row) { + StringRef* strs[2] = {&lhs_value, &rhs_value}; + for (i = 0; i < 2; ++i) { + shapes[i] = + std::shared_ptr(GeoShape::from_encoded(strs[i]->data, strs[i]->size)); + if (shapes[i] == nullptr) { + null_map[row] = 1; + res->insert_default(); + break; + } + } + + if (i == 2) { + auto contains_value = shapes[0]->disjoint(shapes[1].get()); + res->insert_data(const_cast((char*)&contains_value), 0); + } + } + + static void const_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + auto lhs_value = left_column->get_data_at(0); + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto rhs_value = right_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static void vector_const(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + auto rhs_value = right_column->get_data_at(0); + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto lhs_value = left_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static void vector_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto lhs_value = left_column->get_data_at(row); + auto rhs_value = right_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static Status open(FunctionContext* context, FunctionContext::FunctionStateScope scope) { + return Status::OK(); + } + + static Status close(FunctionContext* context, FunctionContext::FunctionStateScope scope) { + return Status::OK(); + } +}; // namespace doris::vectorized + +struct StTouches { + static constexpr auto NEED_CONTEXT = true; + static constexpr auto NAME = "st_touches"; + static const size_t NUM_ARGS = 2; + using Type = DataTypeUInt8; + static Status execute(FunctionContext* context, 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 = ColumnUInt8::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 void loop_do(StringRef& lhs_value, StringRef& rhs_value, + std::vector>& shapes, int& i, + ColumnUInt8::MutablePtr& res, NullMap& null_map, int row) { + StringRef* strs[2] = {&lhs_value, &rhs_value}; + for (i = 0; i < 2; ++i) { + shapes[i] = + std::shared_ptr(GeoShape::from_encoded(strs[i]->data, strs[i]->size)); + if (shapes[i] == nullptr) { + null_map[row] = 1; + res->insert_default(); + break; + } + } + + if (i == 2) { + auto contains_value = shapes[0]->touches(shapes[1].get()); + res->insert_data(const_cast((char*)&contains_value), 0); + } + } + + static void const_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + auto lhs_value = left_column->get_data_at(0); + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto rhs_value = right_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static void vector_const(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + auto rhs_value = right_column->get_data_at(0); + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto lhs_value = left_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static void vector_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, + ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { + int i; + std::vector> shapes = {nullptr, nullptr}; + for (int row = 0; row < size; ++row) { + auto lhs_value = left_column->get_data_at(row); + auto rhs_value = right_column->get_data_at(row); + loop_do(lhs_value, rhs_value, shapes, i, res, null_map, row); + } + } + + static Status open(FunctionContext* context, FunctionContext::FunctionStateScope scope) { + return Status::OK(); + } + + static Status close(FunctionContext* context, FunctionContext::FunctionStateScope scope) { + return Status::OK(); + } +}; // namespace doris::vectorized + struct StGeometryFromText { static constexpr auto NAME = "st_geometryfromtext"; static constexpr GeoShapeType shape_type = GEO_SHAPE_ANY; @@ -915,6 +1200,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>(); factory.register_function>(); factory.register_function>>(); factory.register_function>>(); diff --git a/be/test/geo/geo_types_test.cpp b/be/test/geo/geo_types_test.cpp index bf3ada1efb795f..5c2ebf3734e9f3 100644 --- a/be/test/geo/geo_types_test.cpp +++ b/be/test/geo/geo_types_test.cpp @@ -95,6 +95,726 @@ TEST_F(GeoTypesTest, linestring) { } } +TEST_F(GeoTypesTest, point_intersects) { + GeoParseStatus status; + + const char* wkt_linestring = "LINESTRING(-20 0, 20 0)"; + const char* wkt_polygon = "POLYGON((0 0,10 0,10 10,0 10,0 0))"; + + std::unique_ptr line( + GeoShape::from_wkt(wkt_linestring, strlen(wkt_linestring), &status)); + std::unique_ptr polygon( + GeoShape::from_wkt(wkt_polygon, strlen(wkt_polygon), &status)); + ASSERT_NE(nullptr, line.get()); + ASSERT_NE(nullptr, polygon.get()); + + { + // point on the line (center) + GeoPoint point; + point.from_coord(5, 0); + EXPECT_TRUE(point.intersects(line.get())); + } + { + // point at the end of the line + GeoPoint point; + point.from_coord(-20, 0); + EXPECT_TRUE(point.intersects(line.get())); + } + { + // point outside the line + GeoPoint point; + point.from_coord(0, 5); + EXPECT_FALSE(point.intersects(line.get())); + } + + { + // point inside polygons + GeoPoint point; + point.from_coord(5, 5); + EXPECT_TRUE(point.intersects(polygon.get())); + } + { + // point on polygon boundary edges (not vertices) + GeoPoint point; + point.from_coord(5, 0); + EXPECT_TRUE(point.intersects(polygon.get())); + } + { + // point at polygon vertices + GeoPoint point; + point.from_coord(0, 0); + EXPECT_TRUE(point.intersects(polygon.get())); + } + { + // point outside the polygon + GeoPoint point; + point.from_coord(20, 20); + EXPECT_FALSE(point.intersects(polygon.get())); + } + + std::string buf; + polygon->encode_to(&buf); + { + std::unique_ptr shape(GeoShape::from_encoded(buf.data(), buf.size())); + ASSERT_NE(nullptr, shape.get()); + EXPECT_EQ(GEO_SHAPE_POLYGON, shape->type()); + } + { + buf.resize(buf.size() - 1); + std::unique_ptr shape(GeoShape::from_encoded(buf.data(), buf.size())); + EXPECT_EQ(nullptr, shape.get()); + } +} + +TEST_F(GeoTypesTest, linestring_intersects) { + GeoParseStatus status; + + const char* base_line = "LINESTRING(-10 0, 10 0)"; + const char* vertical_line = "LINESTRING(0 -10, 0 10)"; + const char* polygon = "POLYGON((-5 -5,5 -5,5 5,-5 5,-5 -5))"; + + std::unique_ptr base_line_shape( + GeoShape::from_wkt(base_line, strlen(base_line), &status)); + std::unique_ptr vertical_line_shape( + GeoShape::from_wkt(vertical_line, strlen(vertical_line), &status)); + std::unique_ptr polygon_shape(GeoShape::from_wkt(polygon, strlen(polygon), &status)); + ASSERT_NE(nullptr, base_line_shape.get()); + ASSERT_NE(nullptr, vertical_line_shape.get()); + ASSERT_NE(nullptr, polygon_shape.get()); + + // ====================== + // LineString vs Point + // ====================== + { + // point in the middle of the segment + GeoPoint point; + point.from_coord(5, 0); + EXPECT_TRUE(base_line_shape->intersects(&point)); + } + { + // point at the endpoints of the segment + GeoPoint point; + point.from_coord(-10, 0); + EXPECT_TRUE(base_line_shape->intersects(&point)); + } + { + // the point is outside the segment + GeoPoint point; + point.from_coord(0, 5); + EXPECT_FALSE(base_line_shape->intersects(&point)); + } + + // ====================== + // LineString vs LineString + // ====================== + { + // crosswalks + const char* wkt_string = "LINESTRING(-5 5,5 -5)"; + std::unique_ptr cross_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(base_line_shape->intersects(cross_line.get())); + } + { + // partially overlapping lines + const char* wkt_string = "LINESTRING(-5 0,5 0)"; + std::unique_ptr overlap_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(base_line_shape->intersects(overlap_line.get())); + } + { + // end contact line + const char* wkt_string = "LINESTRING(10 0,10 10)"; + std::unique_ptr touch_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(base_line_shape->intersects(touch_line.get())); + } + { + // fully separated lines + const char* wkt_string = "LINESTRING(0 5,10 5)"; + std::unique_ptr separate_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_FALSE(base_line_shape->intersects(separate_line.get())); + } + + // ====================== + // LineString vs Polygon + // ====================== + { + // fully internal + const char* wkt_string = "LINESTRING(-2 0,2 0)"; + std::unique_ptr inner_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(polygon_shape->intersects(inner_line.get())); + } + { + // crossing the border + const char* wkt_string = "LINESTRING(-10 0,10 0)"; + std::unique_ptr cross_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(polygon_shape->intersects(cross_line.get())); + } + { + // along the borderline + const char* wkt_string = "LINESTRING(-5 -5,5 -5)"; + std::unique_ptr edge_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(polygon_shape->intersects(edge_line.get())); + } + { + // only one point + const char* wkt_string = "LINESTRING(-5 -5,-5 -10)"; + std::unique_ptr edge_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(polygon_shape->intersects(edge_line.get())); + } + { + // fully external + const char* wkt_string = "LINESTRING(10 10,20 20)"; + std::unique_ptr outer_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_FALSE(polygon_shape->intersects(outer_line.get())); + } + + std::string buf; + base_line_shape->encode_to(&buf); + { + std::unique_ptr decoded(GeoShape::from_encoded(buf.data(), buf.size())); + ASSERT_NE(nullptr, decoded.get()); + EXPECT_EQ(GEO_SHAPE_LINE_STRING, decoded->type()); + } + { + buf.resize(buf.size() - 2); + std::unique_ptr decoded(GeoShape::from_encoded(buf.data(), buf.size())); + EXPECT_EQ(nullptr, decoded.get()); + } +} + +TEST_F(GeoTypesTest, polygon_intersects) { + GeoParseStatus status; + + const char* base_polygon = "POLYGON((0 0,10 0,10 10,0 10,0 0))"; + const char* test_line = "LINESTRING(-5 5,15 5)"; + const char* overlap_polygon = "POLYGON((5 5,15 5,15 15,5 15,5 5))"; + + std::unique_ptr polygon( + GeoShape::from_wkt(base_polygon, strlen(base_polygon), &status)); + std::unique_ptr line(GeoShape::from_wkt(test_line, strlen(test_line), &status)); + std::unique_ptr other_polygon( + GeoShape::from_wkt(overlap_polygon, strlen(overlap_polygon), &status)); + ASSERT_NE(nullptr, polygon.get()); + ASSERT_NE(nullptr, line.get()); + ASSERT_NE(nullptr, other_polygon.get()); + + // ====================== + // Polygon vs Point + // ====================== + { + GeoPoint point; + point.from_coord(5, 5); + EXPECT_TRUE(polygon->intersects(&point)); + } + { + GeoPoint point; + point.from_coord(5, 0); + EXPECT_TRUE(polygon->intersects(&point)); + } + { + GeoPoint point; + point.from_coord(0, 0); + EXPECT_TRUE(polygon->intersects(&point)); + } + { + GeoPoint point; + point.from_coord(20, 20); + EXPECT_FALSE(polygon->intersects(&point)); + } + + // ====================== + // Polygon vs LineString + // ====================== + { + const char* wkt = "LINESTRING(2 2,8 8)"; + std::unique_ptr inner_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->intersects(inner_line.get())); + } + { + const char* wkt = "LINESTRING(-5 5,15 5)"; + std::unique_ptr cross_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->intersects(cross_line.get())); + } + { + const char* wkt = "LINESTRING(0 0,10 0)"; + std::unique_ptr edge_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->intersects(edge_line.get())); + } + { + const char* wkt = "LINESTRING(20 20,30 30)"; + std::unique_ptr outer_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->intersects(outer_line.get())); + } + + // ====================== + // Polygon vs Polygon + // ====================== + { + const char* wkt = "POLYGON((2 2,8 2,8 8,2 8,2 2))"; + std::unique_ptr small_polygon(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->intersects(small_polygon.get())); + } + { + const char* wkt = "POLYGON((5 5,15 5,15 15,5 15,5 5))"; + std::unique_ptr overlap_polygon(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->intersects(overlap_polygon.get())); + } + { + const char* wkt = "POLYGON((10 0,20 0,20 10,10 10,10 0))"; + std::unique_ptr touch_polygon(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->intersects(touch_polygon.get())); + } + { + const char* wkt = "POLYGON((20 20,30 20,30 30,20 30,20 20))"; + std::unique_ptr separate_polygon(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->intersects(separate_polygon.get())); + } + + std::string buf; + polygon->encode_to(&buf); + { + std::unique_ptr decoded(GeoShape::from_encoded(buf.data(), buf.size())); + ASSERT_NE(nullptr, decoded.get()); + EXPECT_EQ(GEO_SHAPE_POLYGON, decoded->type()); + } + { + buf.resize(buf.size() - 2); + std::unique_ptr decoded(GeoShape::from_encoded(buf.data(), buf.size())); + EXPECT_EQ(nullptr, decoded.get()); + } +} + +TEST_F(GeoTypesTest, circle_intersect) { + GeoParseStatus status; + + GeoCircle circle; + auto res = circle.init(0, 0, 10); + EXPECT_EQ(GEO_PARSE_OK, res); + + // ====================== + // Circle vs Point + // ====================== + { + GeoPoint point; + point.from_coord(0, 10); + EXPECT_TRUE(circle.intersects(&point)); + } + { + GeoPoint point; + point.from_coord(15, 15); + EXPECT_FALSE(circle.intersects(&point)); + } + + // ====================== + // Circle vs LineString + // ====================== + { + const char* wkt = "LINESTRING(-20 0, 20 0)"; + std::unique_ptr line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(circle.intersects(line.get())); + } + { + const char* wkt = "LINESTRING(20 20, 30 30)"; + std::unique_ptr line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(circle.intersects(line.get())); + } + + // ====================== + // Circle vs Polygon + // ====================== + { + const char* wkt = "POLYGON((-5 -5,5 -5,5 5,-5 5,-5 -5))"; + std::unique_ptr poly(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(circle.intersects(poly.get())); + } + { + const char* wkt = "POLYGON((20 20,30 20,30 30,20 30,20 20))"; + std::unique_ptr poly(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(circle.intersects(poly.get())); + } + + // ====================== + // Circle vs Circle + // ====================== + { + GeoCircle other; + other.init(7, 7, 5); + EXPECT_TRUE(circle.intersects(&other)); + } + { + GeoCircle other; + other.init(20, 20, 5); + EXPECT_FALSE(circle.intersects(&other)); + } +} + +TEST_F(GeoTypesTest, point_touches) { + GeoParseStatus status; + + const char* wkt_linestring = "LINESTRING(-20 0, 20 0)"; + const char* wkt_polygon = "POLYGON((0 0,10 0,10 10,0 10,0 0))"; + + std::unique_ptr line( + GeoShape::from_wkt(wkt_linestring, strlen(wkt_linestring), &status)); + std::unique_ptr polygon( + GeoShape::from_wkt(wkt_polygon, strlen(wkt_polygon), &status)); + ASSERT_NE(nullptr, line.get()); + ASSERT_NE(nullptr, polygon.get()); + + { + // point touches the line at the center + GeoPoint point; + point.from_coord(5, 0); + EXPECT_FALSE(point.touches(line.get())); + } + { + // point touches the end of the line + GeoPoint point; + point.from_coord(-20, 0); + EXPECT_TRUE(point.touches(line.get())); + } + { + // point does not touch the line + GeoPoint point; + point.from_coord(0, 5); + EXPECT_FALSE(point.touches(line.get())); + } + + { + // point inside the polygon (does not touch) + GeoPoint point; + point.from_coord(5, 5); + EXPECT_FALSE(point.touches(polygon.get())); + } + { + // point touches the polygon boundary edge (not vertex) + GeoPoint point; + point.from_coord(5, 0); + EXPECT_TRUE(point.touches(polygon.get())); + } + { + // point touches the polygon vertex + GeoPoint point; + point.from_coord(0, 0); + EXPECT_TRUE(point.touches(polygon.get())); + } + { + // point does not touch the polygon + GeoPoint point; + point.from_coord(20, 20); + EXPECT_FALSE(point.touches(polygon.get())); + } + + std::string buf; + polygon->encode_to(&buf); + { + std::unique_ptr shape(GeoShape::from_encoded(buf.data(), buf.size())); + ASSERT_NE(nullptr, shape.get()); + EXPECT_EQ(GEO_SHAPE_POLYGON, shape->type()); + } + { + buf.resize(buf.size() - 1); + std::unique_ptr shape(GeoShape::from_encoded(buf.data(), buf.size())); + EXPECT_EQ(nullptr, shape.get()); + } +} + +TEST_F(GeoTypesTest, linestring_touches) { + GeoParseStatus status; + + const char* base_line = "LINESTRING(-10 0, 10 0)"; + const char* vertical_line = "LINESTRING(0 -10, 0 10)"; + const char* polygon = "POLYGON((-5 -5,5 -5,5 5,-5 5,-5 -5))"; + + std::unique_ptr base_line_shape( + GeoShape::from_wkt(base_line, strlen(base_line), &status)); + std::unique_ptr vertical_line_shape( + GeoShape::from_wkt(vertical_line, strlen(vertical_line), &status)); + std::unique_ptr polygon_shape(GeoShape::from_wkt(polygon, strlen(polygon), &status)); + ASSERT_NE(nullptr, base_line_shape.get()); + ASSERT_NE(nullptr, vertical_line_shape.get()); + ASSERT_NE(nullptr, polygon_shape.get()); + + // ====================== + // LineString vs Point + // ====================== + { + // point in the middle of the segment + GeoPoint point; + point.from_coord(5, 0); + EXPECT_FALSE(base_line_shape->touches(&point)); + } + { + // point at the endpoints of the segment + GeoPoint point; + point.from_coord(-10, 0); + EXPECT_TRUE(base_line_shape->touches(&point)); + } + { + // the point is outside the segment + GeoPoint point; + point.from_coord(0, 5); + EXPECT_FALSE(base_line_shape->touches(&point)); + } + + // ====================== + // LineString vs LineString + // ====================== + { + // crosswalks + const char* wkt_string = "LINESTRING(-5 5, 5 -5)"; + std::unique_ptr cross_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_FALSE(base_line_shape->touches(cross_line.get())); + } + { + // partially overlapping lines + const char* wkt_string = "LINESTRING(-5 0, 5 0)"; + std::unique_ptr overlap_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(base_line_shape->touches(overlap_line.get())); + } + { + // end contact line + const char* wkt_string = "LINESTRING(10 0, 10 10)"; + std::unique_ptr touch_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(base_line_shape->touches(touch_line.get())); + } + { + // fully separated lines + const char* wkt_string = "LINESTRING(0 5, 10 5)"; + std::unique_ptr separate_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_FALSE(base_line_shape->touches(separate_line.get())); + } + + // ====================== + // LineString vs Polygon + // ====================== + { + // fully internal + const char* wkt_string = "LINESTRING(-2 0,2 0)"; + std::unique_ptr inner_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_FALSE(polygon_shape->touches(inner_line.get())); + } + { + // crossing the border + const char* wkt_string = "LINESTRING(-10 0, 10 0)"; + std::unique_ptr cross_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_FALSE(polygon_shape->touches(cross_line.get())); + } + { + // along the borderline + const char* wkt_string = "LINESTRING(-5 -5,5 -5)"; + std::unique_ptr edge_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(polygon_shape->touches(edge_line.get())); + } + { + // along the borderline + const char* wkt_string = "LINESTRING(-20 -5,20 -5)"; + std::unique_ptr edge_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_TRUE(polygon_shape->touches(edge_line.get())); + } + { + // fully external + const char* wkt_string = "LINESTRING(10 10,20 20)"; + std::unique_ptr outer_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_FALSE(polygon_shape->touches(outer_line.get())); + } + + std::string buf; + base_line_shape->encode_to(&buf); + { + std::unique_ptr decoded(GeoShape::from_encoded(buf.data(), buf.size())); + ASSERT_NE(nullptr, decoded.get()); + EXPECT_EQ(GEO_SHAPE_LINE_STRING, decoded->type()); + } + { + buf.resize(buf.size() - 2); + std::unique_ptr decoded(GeoShape::from_encoded(buf.data(), buf.size())); + EXPECT_EQ(nullptr, decoded.get()); + } +} + +TEST_F(GeoTypesTest, polygon_touches) { + GeoParseStatus status; + + const char* base_polygon = "POLYGON((0 0,10 0,10 10,0 10,0 0))"; + const char* test_line = "LINESTRING(-5 5,15 5)"; + const char* overlap_polygon = "POLYGON((5 5,15 5,15 15,5 15,5 5))"; + + std::unique_ptr polygon( + GeoShape::from_wkt(base_polygon, strlen(base_polygon), &status)); + std::unique_ptr line(GeoShape::from_wkt(test_line, strlen(test_line), &status)); + std::unique_ptr other_polygon( + GeoShape::from_wkt(overlap_polygon, strlen(overlap_polygon), &status)); + ASSERT_NE(nullptr, polygon.get()); + ASSERT_NE(nullptr, line.get()); + ASSERT_NE(nullptr, other_polygon.get()); + + // ====================== + // Polygon vs Point + // ====================== + { + GeoPoint point; + point.from_coord(5, 5); + EXPECT_FALSE(polygon->touches(&point)); + } + { + GeoPoint point; + point.from_coord(5, 0); + EXPECT_TRUE(polygon->touches(&point)); + } + { + GeoPoint point; + point.from_coord(0, 0); + EXPECT_TRUE(polygon->touches(&point)); + } + { + GeoPoint point; + point.from_coord(20, 20); + EXPECT_FALSE(polygon->touches(&point)); + } + + // ====================== + // Polygon vs LineString + // ====================== + { + const char* wkt = "LINESTRING(2 2,8 8)"; + std::unique_ptr inner_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->touches(inner_line.get())); + } + { + const char* wkt = "LINESTRING(-5 5,15 5)"; + std::unique_ptr cross_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->touches(cross_line.get())); + } + { + const char* wkt = "LINESTRING(0 0,10 0)"; + std::unique_ptr edge_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->touches(edge_line.get())); + } + { + const char* wkt = "LINESTRING(20 20,30 30)"; + std::unique_ptr outer_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->touches(outer_line.get())); + } + + // ====================== + // Polygon vs Polygon + // ====================== + { + const char* wkt = "POLYGON((2 2,8 2,8 8,2 8,2 2))"; + std::unique_ptr small_polygon(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->touches(small_polygon.get())); + } + { + const char* wkt = "POLYGON((5 5,15 5,15 15,5 15,5 5))"; + std::unique_ptr overlap_polygon(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->touches(overlap_polygon.get())); + } + { + const char* wkt = "POLYGON((10 0,20 0,20 10,10 10,10 0))"; + std::unique_ptr touch_polygon(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->touches(touch_polygon.get())); + } + { + const char* wkt = "POLYGON((20 20,30 20,30 30,20 30,20 20))"; + std::unique_ptr separate_polygon(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->touches(separate_polygon.get())); + } + + std::string buf; + polygon->encode_to(&buf); + { + std::unique_ptr decoded(GeoShape::from_encoded(buf.data(), buf.size())); + ASSERT_NE(nullptr, decoded.get()); + EXPECT_EQ(GEO_SHAPE_POLYGON, decoded->type()); + } + { + buf.resize(buf.size() - 2); + std::unique_ptr decoded(GeoShape::from_encoded(buf.data(), buf.size())); + EXPECT_EQ(nullptr, decoded.get()); + } +} + +TEST_F(GeoTypesTest, circle_touches) { + GeoParseStatus status; + + GeoCircle circle; + auto res = circle.init(0, 0, 10); + EXPECT_EQ(GEO_PARSE_OK, res); + + // ====================== + // Circle vs Point + // ====================== + { + GeoPoint point; + point.from_coord(0, 10); + EXPECT_TRUE(circle.touches(&point)); + } + { + GeoPoint point; + point.from_coord(15, 15); + EXPECT_FALSE(circle.touches(&point)); + } + + // ====================== + // Circle vs LineString + // ====================== + { + const char* wkt = "LINESTRING(-20 0, 20 0)"; + std::unique_ptr line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(circle.touches(line.get())); + } + { + const char* wkt = "LINESTRING(20 20, 30 30)"; + std::unique_ptr line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(circle.touches(line.get())); + } + + // ====================== + // Circle vs Polygon + // ====================== + { + const char* wkt = "POLYGON((-5 -5,5 -5,5 5,-5 5,-5 -5))"; + std::unique_ptr poly(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(circle.touches(poly.get())); + } + { + const char* wkt = "POLYGON((10 0,20 0,20 10,10 10,10 0))"; + std::unique_ptr poly(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(circle.touches(poly.get())); + } + + // ====================== + // Circle vs Circle + // ====================== + { + GeoCircle other; + other.init(7, 7, 5); + EXPECT_FALSE(circle.touches(&other)); + } + { + GeoCircle other; + other.init(20, 0, 10); + EXPECT_TRUE(circle.touches(&other)); + } +} + TEST_F(GeoTypesTest, polygon_contains) { const char* wkt = "POLYGON ((10 10, 50 10, 50 10, 50 50, 50 50, 10 50, 10 10))"; GeoParseStatus status; 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 e482b419ac5613..0041eadc20e762 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 @@ -403,17 +403,20 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.StAzimuth; 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.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.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.StLinefromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinestringfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPoint; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolyfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolygon; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolygonfromtext; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StTouches; import org.apache.doris.nereids.trees.expressions.functions.scalar.StX; import org.apache.doris.nereids.trees.expressions.functions.scalar.StY; import org.apache.doris.nereids.trees.expressions.functions.scalar.StartsWith; @@ -894,6 +897,9 @@ public class BuiltinScalarFunctions implements FunctionHelper { scalar(StAswkt.class, "st_aswkt"), scalar(StCircle.class, "st_circle"), scalar(StContains.class, "st_contains"), + scalar(StIntersects.class, "st_intersects"), + scalar(StDisjoint.class, "st_disjoint"), + scalar(StTouches.class, "st_touches"), 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/StDisjoint.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StDisjoint.java new file mode 100644 index 00000000000000..2c516a2d67f541 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StDisjoint.java @@ -0,0 +1,70 @@ +// 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.BooleanType; +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_disjoint'. This class is generated by GenerateFunction. + */ +public class StDisjoint extends ScalarFunction + implements BinaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(BooleanType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT) + ); + + /** + * constructor with 2 arguments. + */ + public StDisjoint(Expression arg0, Expression arg1) { + super("st_disjoint", arg0, arg1); + } + + /** + * withChildren. + */ + @Override + public StDisjoint withChildren(List children) { + Preconditions.checkArgument(children.size() == 2); + return new StDisjoint(children.get(0), children.get(1)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStDisjoint(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StIntersects.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StIntersects.java new file mode 100644 index 00000000000000..4351d5ea3aaf0c --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StIntersects.java @@ -0,0 +1,70 @@ +// 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.BooleanType; +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_intersects'. This class is generated by GenerateFunction. + */ +public class StIntersects extends ScalarFunction + implements BinaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(BooleanType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT) + ); + + /** + * constructor with 2 arguments. + */ + public StIntersects(Expression arg0, Expression arg1) { + super("st_intersects", arg0, arg1); + } + + /** + * withChildren. + */ + @Override + public StIntersects withChildren(List children) { + Preconditions.checkArgument(children.size() == 2); + return new StIntersects(children.get(0), children.get(1)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStIntersects(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StTouches.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StTouches.java new file mode 100644 index 00000000000000..ccf2691f138b0f --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StTouches.java @@ -0,0 +1,70 @@ +// 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.BooleanType; +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_touches'. This class is generated by GenerateFunction. + */ +public class StTouches extends ScalarFunction + implements BinaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(BooleanType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT) + ); + + /** + * constructor with 2 arguments. + */ + public StTouches(Expression arg0, Expression arg1) { + super("st_touches", arg0, arg1); + } + + /** + * withChildren. + */ + @Override + public StTouches withChildren(List children) { + Preconditions.checkArgument(children.size() == 2); + return new StTouches(children.get(0), children.get(1)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStTouches(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 4346fb71591768..3009d5b6bbcc23 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 @@ -401,17 +401,20 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.StAzimuth; 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.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.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.StLinefromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinestringfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPoint; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolyfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolygon; import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolygonfromtext; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StTouches; import org.apache.doris.nereids.trees.expressions.functions.scalar.StX; import org.apache.doris.nereids.trees.expressions.functions.scalar.StY; import org.apache.doris.nereids.trees.expressions.functions.scalar.StartsWith; @@ -1957,6 +1960,18 @@ default R visitStContains(StContains stContains, C context) { return visitScalarFunction(stContains, context); } + default R visitStIntersects(StIntersects stIntersects, C context) { + return visitScalarFunction(stIntersects, context); + } + + default R visitStDisjoint(StDisjoint stDisjoint, C context) { + return visitScalarFunction(stDisjoint, context); + } + + default R visitStTouches(StTouches stTouches, C context) { + return visitScalarFunction(stTouches, context); + } + default R visitStDistanceSphere(StDistanceSphere stDistanceSphere, C context) { return visitScalarFunction(stDistanceSphere, context); } diff --git a/gensrc/script/doris_builtins_functions.py b/gensrc/script/doris_builtins_functions.py index 829293bf6c3414..3182476b3c30b8 100644 --- a/gensrc/script/doris_builtins_functions.py +++ b/gensrc/script/doris_builtins_functions.py @@ -2063,7 +2063,10 @@ [['ST_Circle'], 'VARCHAR', ['DOUBLE', 'DOUBLE', 'DOUBLE'], 'ALWAYS_NULLABLE'], - [['ST_Contains'], 'BOOLEAN', ['VARCHAR', 'VARCHAR'], 'ALWAYS_NULLABLE'] + [['ST_Contains'], 'BOOLEAN', ['VARCHAR', 'VARCHAR'], 'ALWAYS_NULLABLE'], + [['ST_Intersects'], 'BOOLEAN', ['VARCHAR', 'VARCHAR'], 'ALWAYS_NULLABLE'], + [['ST_Disjoint'], 'BOOLEAN', ['VARCHAR', 'VARCHAR'], 'ALWAYS_NULLABLE'], + [['ST_Touches'], 'BOOLEAN', ['VARCHAR', 'VARCHAR'], 'ALWAYS_NULLABLE'] ], # grouping sets functions 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 df93348581bd64..4b824bf323c604 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 @@ -14,6 +14,192 @@ true -- !sql -- false +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + -- !sql -- 7336.913554999592 diff --git a/regression-test/data/query_p0/sql_functions/spatial_functions/test_gis_function.out b/regression-test/data/query_p0/sql_functions/spatial_functions/test_gis_function.out index 59bc628249f030..8f86e42ddafff7 100644 --- a/regression-test/data/query_p0/sql_functions/spatial_functions/test_gis_function.out +++ b/regression-test/data/query_p0/sql_functions/spatial_functions/test_gis_function.out @@ -14,6 +14,192 @@ true -- !sql -- false +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +false + +-- !sql -- +true + +-- !sql -- +false + +-- !sql -- +false + -- !sql -- 7336.913554999592 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 8c384f51ff7048..4938790454ae37 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 @@ -27,6 +27,87 @@ suite("test_gis_function") { qt_sql "SELECT ST_Contains(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 5));" qt_sql "SELECT ST_Contains(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(50, 50));" + qt_sql "SELECT ST_Intersects(ST_Point(0, 0), ST_Point(0, 0));" + qt_sql "SELECT ST_Intersects(ST_Point(0, 0), ST_Point(5, 5));" + + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(2, 0));" + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(0, 1));" + + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_LineFromText(\"LINESTRING (2 0, 3 0)\"));" + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_LineFromText(\"LINESTRING (3 0, 4 0)\"));" + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_LineFromText(\"LINESTRING (1 0, 4 0)\"));" + + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 0));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 5));" + + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (20 0, 0 20)\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (-20 0, 20 0)\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (3 5, 8 5)\"));" + + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((5 0, 15 0, 15 10, 5 10, 5 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((10 0, 10 10, 20 10, 20 0, 10 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((11 0, 11 10, 21 10, 21 0, 11 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))\"));" + + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Point(2, 1));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Point(1, 1));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Point(3, 1));" + + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (2 0, 2 2)\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (1.7 0, 1.7 2)\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (1 0.5, 1 1.5)\"));" + + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(5, 5, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(2, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(0, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Circle(3, 1, 1));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Circle(2, 1, 1));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Circle(4, 1, 1));" + + qt_sql "SELECT ST_Touches(ST_Point(0, 0), ST_Point(0, 0));" + qt_sql "SELECT ST_Touches(ST_Point(0, 0), ST_Point(5, 5));" + + qt_sql "SELECT ST_Touches(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Touches(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(2, 0));" + qt_sql "SELECT ST_Touches(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(0, 1));" + + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 0));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 5));" + + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (20 0, 0 20)\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (-20 0, 20 0)\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (3 5, 8 5)\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (-3 5, 8 5)\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (-3 5, 15 5)\"));" + + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((5 0, 15 0, 15 10, 5 10, 5 0))\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((10 0, 10 10, 20 10, 20 0, 10 0))\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((11 0, 11 10, 21 10, 21 0, 11 0))\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))\"));" + + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Point(2, 1));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Point(1, 2));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Point(1, 1));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Point(3, 1));" + + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (2 0, 2 2)\"));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (1.7 0, 1.7 2)\"));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (1 0.5, 1 1.5)\"));" + + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Touches(ST_Circle(5, 5, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Touches(ST_Circle(2, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Touches(ST_Circle(0, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Circle(3, 1, 1));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Circle(2, 1, 1));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Circle(4, 1, 1));" + qt_sql "SELECT ST_DISTANCE_SPHERE(116.35620117, 39.939093, 116.4274406433, 39.9020987219);" qt_sql "SELECT ST_ANGLE_SPHERE(116.35620117, 39.939093, 116.4274406433, 39.9020987219);" qt_sql "SELECT ST_ANGLE_SPHERE(0, 0, 45, 0);" diff --git a/regression-test/suites/query_p0/sql_functions/spatial_functions/test_gis_function.groovy b/regression-test/suites/query_p0/sql_functions/spatial_functions/test_gis_function.groovy index 81eadfb0cc039d..f42b40db54e751 100644 --- a/regression-test/suites/query_p0/sql_functions/spatial_functions/test_gis_function.groovy +++ b/regression-test/suites/query_p0/sql_functions/spatial_functions/test_gis_function.groovy @@ -25,6 +25,87 @@ suite("test_gis_function", "arrow_flight_sql") { qt_sql "SELECT ST_Contains(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 5));" qt_sql "SELECT ST_Contains(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(50, 50));" + qt_sql "SELECT ST_Intersects(ST_Point(0, 0), ST_Point(0, 0));" + qt_sql "SELECT ST_Intersects(ST_Point(0, 0), ST_Point(5, 5));" + + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(2, 0));" + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(0, 1));" + + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_LineFromText(\"LINESTRING (2 0, 3 0)\"));" + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_LineFromText(\"LINESTRING (3 0, 4 0)\"));" + qt_sql "SELECT ST_Intersects(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_LineFromText(\"LINESTRING (1 0, 4 0)\"));" + + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 0));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 5));" + + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (20 0, 0 20)\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (-20 0, 20 0)\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (3 5, 8 5)\"));" + + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((5 0, 15 0, 15 10, 5 10, 5 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((10 0, 10 10, 20 10, 20 0, 10 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((11 0, 11 10, 21 10, 21 0, 11 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))\"));" + + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Point(2, 1));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Point(1, 1));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Point(3, 1));" + + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (2 0, 2 2)\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (1.7 0, 1.7 2)\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (1 0.5, 1 1.5)\"));" + + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(5, 5, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(2, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Intersects(ST_Circle(0, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Circle(3, 1, 1));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Circle(2, 1, 1));" + qt_sql "SELECT ST_Intersects(ST_Circle(1, 1, 1), ST_Circle(4, 1, 1));" + + qt_sql "SELECT ST_Touches(ST_Point(0, 0), ST_Point(0, 0));" + qt_sql "SELECT ST_Touches(ST_Point(0, 0), ST_Point(5, 5));" + + qt_sql "SELECT ST_Touches(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Touches(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(2, 0));" + qt_sql "SELECT ST_Touches(ST_LineFromText(\"LINESTRING (-2 0, 2 0)\"), ST_Point(0, 1));" + + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(0, 0));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 0));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Point(5, 5));" + + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (20 0, 0 20)\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (-20 0, 20 0)\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (3 5, 8 5)\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (-3 5, 8 5)\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_LineFromText(\"LINESTRING (-3 5, 15 5)\"));" + + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((5 0, 15 0, 15 10, 5 10, 5 0))\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((10 0, 10 10, 20 10, 20 0, 10 0))\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((11 0, 11 10, 21 10, 21 0, 11 0))\"));" + qt_sql "SELECT ST_Touches(ST_Polygon(\"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))\"), ST_Polygon(\"POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))\"));" + + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Point(2, 1));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Point(1, 2));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Point(1, 1));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Point(3, 1));" + + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (2 0, 2 2)\"));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (1.7 0, 1.7 2)\"));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_LineFromText(\"LINESTRING (1 0.5, 1 1.5)\"));" + + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Touches(ST_Circle(5, 5, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Touches(ST_Circle(2, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + qt_sql "SELECT ST_Touches(ST_Circle(0, 1, 1), ST_Polygon(\"POLYGON ((2 0, 12 0, 12 10, 2 10, 2 0))\"));" + + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Circle(3, 1, 1));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Circle(2, 1, 1));" + qt_sql "SELECT ST_Touches(ST_Circle(1, 1, 1), ST_Circle(4, 1, 1));" + qt_sql "SELECT ST_DISTANCE_SPHERE(116.35620117, 39.939093, 116.4274406433, 39.9020987219);" qt_sql "SELECT ST_ANGLE_SPHERE(116.35620117, 39.939093, 116.4274406433, 39.9020987219);" From 5579716cacf37e2f896779513852477ca48b9516 Mon Sep 17 00:00:00 2001 From: koi2000 Date: Wed, 5 Mar 2025 17:44:55 +0800 Subject: [PATCH 2/2] [Fix] Fix linestring vs linestring and linestring vs polygon bug in ST_touches function (#48203) --- be/src/geo/geo_types.cpp | 41 +++++++++------------------------- be/test/geo/geo_types_test.cpp | 36 +++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/be/src/geo/geo_types.cpp b/be/src/geo/geo_types.cpp index 0fe7fedbe92d56..e06866d3b5a84b 100644 --- a/be/src/geo/geo_types.cpp +++ b/be/src/geo/geo_types.cpp @@ -547,34 +547,11 @@ bool line_touches_line(const S2Polyline* line1, const S2Polyline* line2) { // The intersection is judged to satisfy the touch condition // if and only if the intersecting points lie on the boundary for (const S2Point& point : cross_points) { - if (!(point == line1->vertex(0) || point == line1->vertex(line1->num_vertices() - 1) || - point == line2->vertex(0) || point == line2->vertex(line2->num_vertices() - 1))) { + if (!(point == line1->vertex(0) || point == line1->vertex(line1->num_vertices() - 1))) { return false; } } - // when no intersections are collected but intersects, the touches condition is satisfied - if (cross_points.empty()) { - int next_vertex = 0; - for (int i = 0; i < line1->num_vertices(); i++) { - const S2Point& p = line1->vertex(i); - S2Point closest_point = line2->Project(p, &next_vertex); - S1Angle distance = S1Angle(closest_point, p); - if (distance.radians() < 1e-2) { - return true; - } - } - - for (int i = 0; i < line2->num_vertices(); i++) { - const S2Point& p = line2->vertex(i); - S2Point closest_point = line1->Project(p, &next_vertex); - S1Angle distance = S1Angle(closest_point, p); - if (distance.radians() < 1e-2) { - return true; - } - } - return false; - } - return true; + return !cross_points.empty(); } bool GeoLine::touches(const GeoShape* rhs) const { @@ -788,15 +765,17 @@ bool GeoPolygon::touches(const GeoShape* rhs) const { return true; } } - } else { - for (auto& intersect_line : intersect_lines) { - if (!line_touches_line(intersect_line.get(), line->polyline())) { - return false; + for (int i = 0; i < line->polyline()->num_vertices(); i++) { + const S2Point& p = line->polyline()->vertex(i); + S2Point closest_point = _polygon->ProjectToBoundary(p); + S1Angle distance(closest_point, p); + if (distance.radians() < 1e-2) { + return true; } } - return true; + return false; } - return false; + return true; } case GEO_SHAPE_POLYGON: { const GeoPolygon* other = (const GeoPolygon*)rhs; diff --git a/be/test/geo/geo_types_test.cpp b/be/test/geo/geo_types_test.cpp index 5c2ebf3734e9f3..1ed478cca954e2 100644 --- a/be/test/geo/geo_types_test.cpp +++ b/be/test/geo/geo_types_test.cpp @@ -579,7 +579,7 @@ TEST_F(GeoTypesTest, linestring_touches) { const char* wkt_string = "LINESTRING(-5 0, 5 0)"; std::unique_ptr overlap_line( GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); - EXPECT_TRUE(base_line_shape->touches(overlap_line.get())); + EXPECT_FALSE(base_line_shape->touches(overlap_line.get())); } { // end contact line @@ -588,6 +588,13 @@ TEST_F(GeoTypesTest, linestring_touches) { GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); EXPECT_TRUE(base_line_shape->touches(touch_line.get())); } + { + // end intersect line + const char* wkt_string = "LINESTRING(9 0, 10 10)"; + std::unique_ptr touch_line( + GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); + EXPECT_FALSE(base_line_shape->touches(touch_line.get())); + } { // fully separated lines const char* wkt_string = "LINESTRING(0 5, 10 5)"; @@ -615,7 +622,7 @@ TEST_F(GeoTypesTest, linestring_touches) { } { // along the borderline - const char* wkt_string = "LINESTRING(-5 -5,5 -5)"; + const char* wkt_string = "LINESTRING(-5 -5, 5 -5)"; std::unique_ptr edge_line( GeoShape::from_wkt(wkt_string, strlen(wkt_string), &status)); EXPECT_TRUE(polygon_shape->touches(edge_line.get())); @@ -702,6 +709,31 @@ TEST_F(GeoTypesTest, polygon_touches) { std::unique_ptr cross_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); EXPECT_FALSE(polygon->touches(cross_line.get())); } + { + const char* wkt = "LINESTRING(10 5, 15 5)"; + std::unique_ptr cross_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->touches(cross_line.get())); + } + { + const char* wkt = "LINESTRING(5 5, 15 15)"; + std::unique_ptr cross_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_FALSE(polygon->touches(cross_line.get())); + } + { + const char* wkt = "LINESTRING(10 10, 15 15)"; + std::unique_ptr cross_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->touches(cross_line.get())); + } + { + const char* wkt = "LINESTRING(0 0, 5 0)"; + std::unique_ptr edge_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->touches(edge_line.get())); + } + { + const char* wkt = "LINESTRING(2 0, 5 0)"; + std::unique_ptr edge_line(GeoShape::from_wkt(wkt, strlen(wkt), &status)); + EXPECT_TRUE(polygon->touches(edge_line.get())); + } { const char* wkt = "LINESTRING(0 0,10 0)"; std::unique_ptr edge_line(GeoShape::from_wkt(wkt, strlen(wkt), &status));