From 4b720b027d1dc76d8996afc5936cd3ed2e044be2 Mon Sep 17 00:00:00 2001 From: Alexey Botchkov Date: Fri, 20 Dec 2024 16:01:03 -0500 Subject: [PATCH] MDEV-35126 Wrong results from st_isvalid for multipolygon. The Gis_polygon::is_valid() and the Gis_multi_polygon::is_valid() implemented with precise geometry engine. --- .../spatial_utility_function_isvalid.result | 39 ++ .../spatial_utility_function_isvalid.test | 28 ++ sql/gcalc_slicescan.h | 1 + sql/gcalc_tools.cc | 11 +- sql/gcalc_tools.h | 36 +- sql/gstream.h | 19 +- sql/spatial.cc | 457 +++++++++++++++--- sql/spatial.h | 13 + 8 files changed, 510 insertions(+), 94 deletions(-) diff --git a/mysql-test/main/spatial_utility_function_isvalid.result b/mysql-test/main/spatial_utility_function_isvalid.result index 67f3faa57d1..816956aacd1 100644 --- a/mysql-test/main/spatial_utility_function_isvalid.result +++ b/mysql-test/main/spatial_utility_function_isvalid.result @@ -404,3 +404,42 @@ create or replace table tb1 as SELECT st_validate(POINTFROMTEXT(' POINT( 4 1 ) create or replace table tb1 as SELECT st_validate(ST_GeomFromText (' linestring( 4 1,4 4 ) ')) a; create table tb2 as SELECT (st_validate (ST_collect(( POINTFROMTEXT(' POINT( 4 1 ) ') )) )) a; drop table tb1, tb2; +######################################################################## +# MDEV-35126 Wrong results from st_isvalid or st_validate function, while using multipolygon. +######################################################################## +select ST_isvalid(ST_GEOMFROMTEXT('multipolygon(((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)), ((59 18,67 18,67 13,59 13,59 18)))')); +ST_isvalid(ST_GEOMFROMTEXT('multipolygon(((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)), ((59 18,67 18,67 13,59 13,59 18)))')) +1 +select ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 6 0, 1 2, 0 0)), (( 7 7, 1 8, 7 0, 7 7 ))) ')); +ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 6 0, 1 2, 0 0)), (( 7 7, 1 8, 7 0, 7 7 ))) ')) +1 +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((70 50, 80 50, 80 60, 70 60, 70 50)))')); +ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((70 50, 80 50, 80 60, 70 60, 70 50)))')) +0 +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((90 50, 100 50, 100 60, 90 60, 90 50)))')); +ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((90 50, 100 50, 100 60, 90 60, 90 50)))')) +0 +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((60 20, 70 20, 70 30, 60 30, 60 20)))')); +ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((60 20, 70 20, 70 30, 60 30, 60 20)))')) +1 +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 80 200, 110 210, 140 270, 70 280, 40 250, 60 220, 80 200)), (( 120 210, 160 260, 140 260, 120 210)))')); +ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 80 200, 110 210, 140 270, 70 280, 40 250, 60 220, 80 200)), (( 120 210, 160 260, 140 260, 120 210)))')) +1 +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 80 200, 110 210, 140 270, 70 280, 40 250, 60 220, 80 200)), (( 90 220, 110 240, 60 250, 90 220)))')); +ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 80 200, 110 210, 140 270, 70 280, 40 250, 60 220, 80 200)), (( 90 220, 110 240, 60 250, 90 220)))')) +0 +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 5 5, 5 0, 0 5, 0 0)), (( 2 0, 3 0, 2 1, 2 0 ))) ')); +ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 5 5, 5 0, 0 5, 0 0)), (( 2 0, 3 0, 2 1, 2 0 ))) ')) +0 +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 3 0, 0 3, 0 0)), (( 3 0, 3 3, 0 3, 3 0))) ')); +ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 3 0, 0 3, 0 0)), (( 3 0, 3 3, 0 3, 3 0))) ')) +0 +SELECT ST_IsValid(ST_GEOMFROMTEXT(' POLYGON( ( 0 0, 0 0, 8 0, 0 0 ) ) ')); +ST_IsValid(ST_GEOMFROMTEXT(' POLYGON( ( 0 0, 0 0, 8 0, 0 0 ) ) ')) +0 +SELECT ST_IsValid(ST_GEOMFROMTEXT(' MULTIPOLYGON( ( ( 2 2, 2 8, 8 8, 8 2, 2 2 ), ( 4 4, 4 6, 6 6, 6 4, 4 4 ) ), ( ( 2 2, 1 2, 0 5, 2 9, 2 2 ) ) ) ')) c; +c +0 +SELECT ST_IsValid(ST_GEOMFROMTEXT(' MULTIPOLYGON( ( (2 2, 2 4, 4 4, 4 2, 2 2) ), ( (3 5, 2 5, 2 4, 3 4, 3 5) ) ) ')); +ST_IsValid(ST_GEOMFROMTEXT(' MULTIPOLYGON( ( (2 2, 2 4, 4 4, 4 2, 2 2) ), ( (3 5, 2 5, 2 4, 3 4, 3 5) ) ) ')) +0 diff --git a/mysql-test/main/spatial_utility_function_isvalid.test b/mysql-test/main/spatial_utility_function_isvalid.test index 9df1050e40d..12b2080db38 100644 --- a/mysql-test/main/spatial_utility_function_isvalid.test +++ b/mysql-test/main/spatial_utility_function_isvalid.test @@ -387,3 +387,31 @@ create or replace table tb1 as SELECT st_validate(POINTFROMTEXT(' POINT( 4 1 ) create or replace table tb1 as SELECT st_validate(ST_GeomFromText (' linestring( 4 1,4 4 ) ')) a; create table tb2 as SELECT (st_validate (ST_collect(( POINTFROMTEXT(' POINT( 4 1 ) ') )) )) a; drop table tb1, tb2; + +--echo ######################################################################## +--echo # MDEV-35126 Wrong results from st_isvalid or st_validate function, while using multipolygon. +--echo ######################################################################## +select ST_isvalid(ST_GEOMFROMTEXT('multipolygon(((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)), ((59 18,67 18,67 13,59 13,59 18)))')); + +select ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 6 0, 1 2, 0 0)), (( 7 7, 1 8, 7 0, 7 7 ))) ')); + +# Concave shapes are not well-supported. This is correctly marked as invalid. +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((70 50, 80 50, 80 60, 70 60, 70 50)))')); + +# Concave shapes are not well-supported. This is correctly marked as invalid. +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((90 50, 100 50, 100 60, 90 60, 90 50)))')); + +# Concave shapes are not well-supported. This is incorrectly marked as invalid. +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 40 40, 80 40, 100 0, 120 40, 160 40, 130 70, 150 110, 100 90, 50 110, 70 70, 40 40)), ((60 20, 70 20, 70 30, 60 30, 60 20)))')); + +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 80 200, 110 210, 140 270, 70 280, 40 250, 60 220, 80 200)), (( 120 210, 160 260, 140 260, 120 210)))')); + +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 80 200, 110 210, 140 270, 70 280, 40 250, 60 220, 80 200)), (( 90 220, 110 240, 60 250, 90 220)))')); + +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 5 5, 5 0, 0 5, 0 0)), (( 2 0, 3 0, 2 1, 2 0 ))) ')); +SELECT ST_isvalid(ST_GEOMFROMTEXT(' MULTIPOLYGON((( 0 0, 3 0, 0 3, 0 0)), (( 3 0, 3 3, 0 3, 3 0))) ')); + +SELECT ST_IsValid(ST_GEOMFROMTEXT(' POLYGON( ( 0 0, 0 0, 8 0, 0 0 ) ) ')); +SELECT ST_IsValid(ST_GEOMFROMTEXT(' MULTIPOLYGON( ( ( 2 2, 2 8, 8 8, 8 2, 2 2 ), ( 4 4, 4 6, 6 6, 6 4, 4 4 ) ), ( ( 2 2, 1 2, 0 5, 2 9, 2 2 ) ) ) ')) c; +SELECT ST_IsValid(ST_GEOMFROMTEXT(' MULTIPOLYGON( ( (2 2, 2 4, 4 4, 4 2, 2 2) ), ( (3 5, 2 5, 2 4, 3 4, 3 5) ) ) ')); + diff --git a/sql/gcalc_slicescan.h b/sql/gcalc_slicescan.h index ac37802d7d4..e5c2873c696 100644 --- a/sql/gcalc_slicescan.h +++ b/sql/gcalc_slicescan.h @@ -252,6 +252,7 @@ public: #endif /*GCALC_CHECK_WITH_FLOAT*/ double coord_extent; Gcalc_dyn_list::Item **get_cur_hook() { return m_hook; } + int get_n_points() const { return m_n_points; } private: Gcalc_dyn_list::Item *m_first; diff --git a/sql/gcalc_tools.cc b/sql/gcalc_tools.cc index 49d9a4ac58b..450a955c9b5 100644 --- a/sql/gcalc_tools.cc +++ b/sql/gcalc_tools.cc @@ -131,7 +131,7 @@ int Gcalc_function::count_internal(const char *cur_func, uint set_type, uint n_ops= c_op & ~(op_any | op_not | v_mask); uint n_shape= c_op & ~(op_any | op_not | v_mask); /* same as n_ops */ op_type v_state= (op_type) (c_op & v_mask); - int result= 0; + int result= 0, t_counter; const char *sav_cur_func= cur_func; // GCALC_DBUG_ENTER("Gcalc_function::count_internal"); @@ -175,6 +175,11 @@ int Gcalc_function::count_internal(const char *cur_func, uint set_type, //GCALC_DBUG_RETURN(mask); result= count_internal(cur_func, set_type, &cur_func); + if (next_func == op_any_intersection) + { + t_counter= result == result_true; + result= result_false; + } while (--n_ops) { @@ -211,6 +216,10 @@ int Gcalc_function::count_internal(const char *cur_func, uint set_type, else result= result_true; break; + case op_any_intersection: + t_counter+= next_res == result_true; + result= (t_counter > 1) ? result_true : result_false; + break; default: GCALC_DBUG_ASSERT(FALSE); }; diff --git a/sql/gcalc_tools.h b/sql/gcalc_tools.h index f7a05633ace..5cfa1a79abb 100644 --- a/sql/gcalc_tools.h +++ b/sql/gcalc_tools.h @@ -30,6 +30,7 @@ op_intersection ( A && B && C ... ) op_symdifference ( A+B+C+... == 1 ) op_difference ( A && !(B||C||..)) + op_any_intersection ( A && B || A && C || ... || B && C ... ) with the calls of the add_operation(operation, n_operands) method. The relation is calculated over a set of shapes, that in turn have to be added with the add_new_shape() method. All the 'shapes' can @@ -55,23 +56,24 @@ public: enum op_type { v_empty= 0x00000000, - v_find_t= 0x01000000, - v_find_f= 0x02000000, - v_t_found= 0x03000000, - v_f_found= 0x04000000, - v_mask= 0x07000000, + v_find_t= 0x00100000, + v_find_f= 0x00200000, + v_t_found= 0x00300000, + v_f_found= 0x00400000, + v_mask= 0x00700000, - op_not= 0x80000000, - op_shape= 0x00000000, - op_union= 0x10000000, - op_intersection= 0x20000000, - op_symdifference= 0x30000000, - op_difference= 0x40000000, - op_repeat= 0x50000000, - op_border= 0x60000000, - op_internals= 0x70000000, - op_false= 0x08000000, - op_any= 0x78000000 /* The mask to get any of the operations */ + op_not= 0x80000000, + op_shape= 0x00000000, + op_union= 0x10000000, + op_intersection= 0x20000000, + op_symdifference= 0x30000000, + op_difference= 0x40000000, + op_repeat= 0x50000000, + op_border= 0x60000000, + op_internals= 0x70000000, + op_false= 0x08000000, + op_any_intersection= 0x07000000, + op_any= 0x7F000000 /* The mask to get any of the operations */ }; enum shape_type { @@ -144,7 +146,7 @@ protected: gcalc_shape_info m_si; public: Gcalc_operation_transporter(Gcalc_function *fn, Gcalc_heap *heap) : - Gcalc_shape_transporter(heap), m_fn(fn) {} + Gcalc_shape_transporter(heap), m_fn(fn), m_si(0) {} int single_point(double x, double y) override; int start_line() override; diff --git a/sql/gstream.h b/sql/gstream.h index c5c715393ac..9b49d15f8bb 100644 --- a/sql/gstream.h +++ b/sql/gstream.h @@ -35,9 +35,18 @@ public: }; Gis_read_stream(CHARSET_INFO *charset, const char *buffer, int size) - :m_cur(buffer), m_limit(buffer + size), m_err_msg(NULL), m_charset(charset) + : m_wkt(buffer) + , m_cur(buffer) + , m_limit(buffer + size) + , m_err_msg(NULL) + , m_charset(charset) {} - Gis_read_stream(): m_cur(NullS), m_limit(NullS), m_err_msg(NullS) + + Gis_read_stream() + : m_wkt(NullS) + , m_cur(NullS) + , m_limit(NullS) + , m_err_msg(NullS) {} ~Gis_read_stream() { @@ -82,7 +91,13 @@ public: return err_msg; } + const char *get_wkt() const + { + return m_wkt; + } + protected: + const char *const m_wkt; const char *m_cur; const char *m_limit; char *m_err_msg; diff --git a/sql/spatial.cc b/sql/spatial.cc index f41b30f57ff..293c04846a5 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -1041,6 +1041,30 @@ const char *Geometry::get_mbr_for_points(MBR *mbr, const char *data, return data; } +const char* Geometry::get_points_common(const char* data, + Geometry::PointContainer &points) const +{ + uint32 expected_points; + if (no_data(data, 4)) + return nullptr; + expected_points= uint4korr(data); + data+= 4; + + if (not_enough_points(data, expected_points, 0)) + return nullptr; + + while (expected_points--) + { + double x, y; + float8get(x, data); + float8get(y, data + SIZEOF_STORED_DOUBLE); + points.push_back(std::make_pair(x, y)); + data+= POINT_DATA_SIZE; + } + return data; +} + + /***************************** Point *******************************/ @@ -2075,73 +2099,210 @@ bool Gis_polygon::get_mbr(MBR *mbr, const char **end) const return 0; } +bool Gis_polygon::get_points(Geometry::PointContainer &points) const +{ + uint32 n_linear_rings; + const char *data= m_data; + + if (no_data(data, 4)) + return true; + n_linear_rings= uint4korr(data); + data+= 4; + + while (data && n_linear_rings--) + data= get_points_common(data, points); + return !data; +} + + +class Gcalc_poly_transporter : public Gcalc_shape_transporter +{ +protected: + gcalc_shape_info m_si; + int m_points_in_ring; + int m_error; +public: + Gcalc_poly_transporter(Gcalc_heap *heap) : + Gcalc_shape_transporter(heap), m_si(0), m_error(0) {} + + int get_error() const { return m_error; } + int single_point(double x, double y) override { return 0; } + int start_line() override { return 0; } + int complete_line() override { return 0; } + + int start_poly() override + { + int_start_poly(); + return 0; + } + + int complete_poly() override + { + int_complete_poly(); + return 0; + } + int start_ring() override + { + int_start_ring(); + m_points_in_ring= m_heap->get_n_points(); + return 0; + } + int complete_ring() override + { + int_complete_ring(); + m_si++; + if (m_heap->get_n_points() - m_points_in_ring < 3) + m_error= 1; + return 0; + } + int add_point(double x, double y) override + { + return int_add_point(m_si, x, y); + } + + int start_collection(int n_objects) override { return 0; } + int empty_shape() override { return 0; } +}; + int Gis_polygon::is_valid(int *valid) const { - Geometry *exterior_ring, *interior_ring; - MBR exterior_mbr, interior_mbr; - uint32 num_interior_ring; - Geometry_buffer buffer; + Gcalc_scan_iterator scan_it; + Gcalc_heap collector; + Gcalc_poly_transporter trn(&collector); + MBR mbr; + uint32 num_rings; const char *c_end; - String wkb= 0; + char *border_count, *touches_count, *internals; + int result= 0; + *valid= 0; - if (wkb.reserve(SRID_SIZE + BYTE_ORDER_SIZE + WKB_HEADER_SIZE)) + if (this->num_interior_ring(&num_rings)) return 1; - wkb.q_append(SRID_PLACEHOLDER); - if (this->exterior_ring(&wkb) || - !(exterior_ring= Geometry::construct(&buffer, wkb.ptr(), wkb.length()))) + num_rings++; + + if(this->get_mbr(&mbr, &c_end)) return 1; - int valid_ring, simple; - if (exterior_ring->is_valid(&valid_ring) || - exterior_ring->is_simple(&simple)) + collector.set_extent(mbr.xmin, mbr.xmax, mbr.ymin, mbr.ymax); + + if (this->store_shapes(&trn)) return 1; - if (!valid_ring || !simple) - return 0; + if (trn.get_error()) + goto exit; - if (exterior_ring->get_mbr(&exterior_mbr, &c_end) || - this->num_interior_ring(&num_interior_ring)) - return 1; + collector.prepare_operation(); + scan_it.init(&collector); - std::vector interior_mbrs; - for(uint32 i= 1; i <= num_interior_ring; i++) + border_count= (char *) my_alloca(num_rings); + bzero(border_count, num_rings); + touches_count= (char *) my_alloca(num_rings); + internals= (char *) my_alloca(num_rings); + + while (scan_it.more_points()) { - String interior_wkb= 0; - if (interior_wkb.reserve(SRID_SIZE + BYTE_ORDER_SIZE + WKB_HEADER_SIZE)) - return 1; + const Gcalc_scan_iterator::event_point *events; - interior_wkb.q_append(SRID_PLACEHOLDER); - if (this->interior_ring_n(i, &interior_wkb)) - break; - - if (!(interior_ring= Geometry::construct(&buffer, interior_wkb.ptr(), - interior_wkb.length())) || - interior_ring->get_mbr(&interior_mbr, &c_end)) - return 1; - - if (!exterior_mbr.contains(&interior_mbr) || - exterior_mbr.equals(&interior_mbr)) - return 0; - - if (interior_ring->is_simple(&simple)) - return 1; - - if (!simple) - return 0; - - for (const auto &mbr : interior_mbrs) + if (scan_it.step()) { - if (interior_mbr.equals(&mbr) || interior_mbr.within(&mbr)) - return 0; + result= 1; + goto exit; + } + + events= scan_it.get_events(); + + Gcalc_point_iterator pit(&scan_it); + int outer_border= 0; + + bzero(internals, num_rings); + /* Walk to the event, marking polygons we met */ + for (; pit.point() != scan_it.get_event_position(); ++pit) + { + gcalc_shape_info si= pit.point()->get_shape(); + internals[si]^= 1; + if (si != 0) /* interior ring */ + { + if (!internals[0]) + { + /* Internal ring outside the outer. */ + goto exit; + } + for (uint n=1; nsimple_event()) + continue; + + bzero(touches_count, num_rings); + + /* Check the status of the event point */ + for (; events; events= events->get_next()) + { + gcalc_shape_info si= events->get_shape(); + if (events->event == scev_thread || + events->event == scev_end || /* should never happen. */ + events->event == scev_single_point || + events->event == scev_intersection) + { + /* These types of events never happen in valid polygon. */ + goto exit; + } + + touches_count[si]++; + if (events->event == scev_two_threads || events->event == scev_two_ends) + { + if (touches_count[si] > 2) + goto exit; + } + else + { + if (touches_count[si] > 1) + goto exit; + } + + if (si == 0) /* outer ring */ + outer_border= 1; + else + { + if (!outer_border && !internals[0]) + { + /* Inner ring outside the outer ring. */ + goto exit; + } + if (outer_border) + { + if (border_count[si]++ > 1) + { + /* + We can't have more than one point of the + internal ring on the border of the outer ring. + */ + goto exit; + } + } + } } - interior_mbrs.push_back(interior_mbr); } *valid= 1; - return 0; + +exit: + collector.reset(); + scan_it.reset(); + my_afree(border_count); + my_afree(touches_count); + my_afree(internals); + + return result; + } @@ -3639,48 +3800,164 @@ bool Gis_multi_polygon::get_data_as_json(String *txt, uint max_dec_digits, } +class Gcalc_multipoly_transporter : public Gcalc_shape_transporter +{ +protected: + gcalc_shape_info m_si; +public: + Gcalc_multipoly_transporter(Gcalc_heap *heap) : + Gcalc_shape_transporter(heap), m_si(0) {} + + int single_point(double x, double y) override { return 0; } + int start_line() override { return 0; } + int complete_line() override { return 0; } + + int start_poly() override + { + int_start_poly(); + return 0; + } + + int complete_poly() override + { + int_complete_poly(); + m_si++; + return 0; + } + int start_ring() override + { + int_start_ring(); + return 0; + } + int complete_ring() override + { + int_complete_ring(); + return 0; + } + int add_point(double x, double y) override + { + return int_add_point(m_si, x, y); + } + + int start_collection(int n_objects) override + { + return 0; + } + + int empty_shape() override { return 0; } +}; + + int Gis_multi_polygon::is_valid(int *valid) const { - Geometry_buffer buffer; + int result= 0; + Gcalc_scan_iterator scan_it; + Gcalc_heap collector; + Gcalc_multipoly_transporter trn(&collector); + MBR mbr; uint32 num_geometries; - std::vector mbrs; - Geometry *geometry; - *valid= 0; + const char *c_end; + char *internals; if (this->num_geometries(&num_geometries)) return 1; - for (uint32 i= 1; i <= num_geometries; i++) + if (shapes_valid(valid)) + return 1; + + if (*valid == 0) + return 0; + + *valid= 0; + + if (num_geometries < 1) + return 0; + + if(this->get_mbr(&mbr, &c_end)) + return 1; + + + collector.set_extent(mbr.xmin, mbr.xmax, mbr.ymin, mbr.ymax); + + if (this->store_shapes(&trn)) + return 1; + + + collector.prepare_operation(); + scan_it.init(&collector); + internals= (char *) my_alloca(num_geometries); + + while (scan_it.more_points()) { - String wkb= 0; - if (wkb.reserve(SRID_SIZE + BYTE_ORDER_SIZE + WKB_HEADER_SIZE)) - return 0; + const Gcalc_scan_iterator::event_point *events, *next_ev; - wkb.q_append(SRID_PLACEHOLDER); - if (this->geometry_n(i, &wkb) || - !(geometry= Geometry::construct(&buffer, wkb.ptr(), wkb.length()))) - return 1; - - int internal_valid; - const char *c_end; - MBR interior_mbr; - if (geometry->is_valid(&internal_valid) || - geometry->get_mbr(&interior_mbr, &c_end)) - return 1; - - if (!internal_valid) - return 0; - - for (const auto &mbr : mbrs) + if (scan_it.step()) { - if (interior_mbr.intersects(&mbr) && !interior_mbr.touches(&mbr)) - return 0; + result= 1; + goto exit; + } + + events= scan_it.get_events(); + + Gcalc_point_iterator pit(&scan_it); + + bzero(internals, num_geometries); + /* Walk to the event, marking polygons we met */ + for (; pit.point() != scan_it.get_event_position(); ++pit) + { + gcalc_shape_info si= pit.point()->get_shape(); + internals[si]^= 1; + } + + if (events->simple_event()) + continue; + + /* Check the status of the event point */ + for (; events; events= events->get_next()) + { + gcalc_shape_info si= events->get_shape(); + if (events->event == scev_thread || + events->event == scev_end || /* should never happen. */ + events->event == scev_single_point || + events->event == scev_intersection) + { + /* These types of events never happen in valid multipolygon. */ + goto exit; + } + + if ((internals[si]^= 1)) + { + for (uint n=0; nget_next())) + { + if (next_ev->event != scev_two_ends && + events->event != scev_two_ends && + events->cmp_dx_dy(events->dx, events->dy, + next_ev->dx, next_ev->dy) == 0) + { + /* Only can touch at points, not lines. */ + goto exit; + } + } } - mbrs.push_back(interior_mbr); } *valid= 1; - return 0; + +exit: + collector.reset(); + scan_it.reset(); + + return result; } @@ -3892,6 +4169,38 @@ int Gis_multi_polygon::store_shapes(Gcalc_shape_transporter *trn) const } +int Gis_multi_polygon::shapes_valid(int *valid) const +{ + uint32 n_polygons; + Gis_polygon p; + const char *data= m_data; + + if (no_data(data, 4)) + return 1; + n_polygons= uint4korr(data); + data+= 4; + + *valid= 0; + + while (n_polygons--) + { + if (no_data(data, WKB_HEADER_SIZE)) + return 1; + data+= WKB_HEADER_SIZE; + p.set_data_ptr(data, (uint32) (m_data_end - data)); + if (p.is_valid(valid)) + return 1; + + if (*valid == 0) + break; + + data+= p.get_data_size(); + } + + return 0; +} + + int Gis_multi_polygon::make_clockwise(String *result) const { Geometry_buffer buffer; diff --git a/sql/spatial.h b/sql/spatial.h index 9ea47230efe..774824089a7 100644 --- a/sql/spatial.h +++ b/sql/spatial.h @@ -347,6 +347,7 @@ public: static Class_info *ci_collection[wkb_last+1]; static bool create_point(String *result, double x, double y); + using PointContainer = std::vector>; protected: static Class_info *find_class(int type_id) { @@ -359,8 +360,16 @@ protected: bool create_point(String *result, const char *data) const; const char *get_mbr_for_points(MBR *mbr, const char *data, uint offset) const; + const char* get_points_common(const char* data, PointContainer &points) const; public: + virtual bool get_points(Geometry::PointContainer &points) const + { + // TODO implement this override for other types + assert(false); + return true; + } + /** Check if there're enough data remaining as requested @@ -537,6 +546,8 @@ public: int store_shapes(Gcalc_shape_transporter *trn) const override; int make_clockwise(String *result) const override; const Class_info *get_class_info() const override; +private: + bool get_points(Geometry::PointContainer &points) const override; }; @@ -640,6 +651,8 @@ public: int make_clockwise(String *result) const override; const Class_info *get_class_info() const override; uint init_from_opresult(String *bin, const char *opres, uint res_len) override; +private: + int shapes_valid(int *valid) const; };