From b50366667b36265de37b10c9b5801feb6f3dd49f Mon Sep 17 00:00:00 2001 From: Stefano Petrilli Date: Mon, 26 Jan 2015 13:56:12 +0530 Subject: [PATCH] MDEV-34137: Implement the GIS function ST_Validate The GIS function ST_Validate takes ad input a geometry and verifies that - is compliant with the Well-Known Binary (WKB) format and Spatial Reference System Identifier (SRID) syntax. - is geometrically valid. If the input is valid return it, else it returns NULL. The use case of this function is to filter out invalid geometry data. Author: StefanoPetrilli Co-authored-by: Torje Digernes Co-authored-by: Hans H Melby Co-authored-by: Jon Olav Hauglid Co-authored-by: Erlend Dahl Co-authored-by: Norvald H. Ryeng Co-authored-by: David.Zhao Co-authored-by: Pavan --- .../spatial_utility_function_validate.result | 139 ++++++++++++ .../spatial_utility_function_validate.test | 101 +++++++++ sql/item_geofunc.cc | 97 +++++++-- sql/item_geofunc.h | 20 ++ sql/share/errmsg-utf8.txt | 4 + sql/spatial.cc | 199 ++++++++++++++++++ sql/spatial.h | 7 +- 7 files changed, 545 insertions(+), 22 deletions(-) create mode 100644 mysql-test/main/spatial_utility_function_validate.result create mode 100644 mysql-test/main/spatial_utility_function_validate.test diff --git a/mysql-test/main/spatial_utility_function_validate.result b/mysql-test/main/spatial_utility_function_validate.result new file mode 100644 index 00000000000..b9633d2a256 --- /dev/null +++ b/mysql-test/main/spatial_utility_function_validate.result @@ -0,0 +1,139 @@ +# Creating the spatial Geometry object +USE test; +# ST_VALIDATE must return null when its parameter is NULL +SELECT ST_ASTEXT(ST_VALIDATE( NULL )); +ST_ASTEXT(ST_VALIDATE( NULL )) +NULL +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT(NULL,0))); +ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT(NULL,0))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT(NULL,4053))); +ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT(NULL,4053))) +NULL +# ST_VALIDATE raises an error if the data is malformed +SELECT ST_VALIDATE( x'00000000DEADBEEF'); +ERROR 22023: Invalid GIS data provided to function st_validate. +# ST_VALIDATE return the input if it is valid +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POINT(15 25)'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POINT(15 25)'))) +POINT(15 25) +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOINT(5 0,25 0,15 10,15 25)'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOINT(5 0,25 0,15 10,15 25)'))) +MULTIPOINT(5 0,25 0,15 10,15 25) +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING(10 15,20 15)'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING(10 15,20 15)'))) +LINESTRING(10 15,20 15) +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING((25 0,0 15,15 30,0 5))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING((25 0,0 15,15 30,0 5))'))) +MULTILINESTRING((25 0,0 15,15 30,0 5)) +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((5 0,7 10,0 15,10 15,15 25,20 15,30 15,22 10,25 0,15 5,5 0))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((5 0,7 10,0 15,10 15,15 25,20 15,30 15,22 10,25 0,15 5,5 0))'))) +POLYGON((5 0,7 10,0 15,10 15,15 25,20 15,30 15,22 10,25 0,15 5,5 0)) +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((0 0,0 10,10 10,0 0)),((10 10,10 15,15 15,10 10)))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((0 0,0 10,10 10,0 0)),((10 10,10 15,15 15,10 10)))'))) +MULTIPOLYGON(((0 0,0 10,10 10,0 0)),((10 10,10 15,15 15,10 10))) +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(POINT(10 10),' + 'MULTIPOINT(0 0,10 10),' + 'LINESTRING(1 1,2 2,3 3),' + 'MULTILINESTRING((0 0,0 10,10 10,10 0),(10 10,10 15,15 15,10 10)))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(POINT(10 10),' + 'MULTIPOINT(0 0,10 10),' + 'LINESTRING(1 1,2 2,3 3),' + +GEOMETRYCOLLECTION(POINT(10 10),MULTIPOINT(0 0,10 10),LINESTRING(1 1,2 2,3 3),MULTILINESTRING((0 0,0 10,10 10,10 0),(10 10,10 15,15 15,10 10))) +# The only valid empty geometry is the empty geometrycollection +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POINT()'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POINT()'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOINT()'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOINT()'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING()'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING()'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING(())'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING(())'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON(())'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON(())'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON((()),(()))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON((()),(()))'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION()'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION()'))) +GEOMETRYCOLLECTION EMPTY +# Invalid geometries return null +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING(0 0,-0.00 0,0.0 0)'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING(0 0,-0.00 0,0.0 0)'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING((0 0))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING((0 0))'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((0 0))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((0 0))'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((1 1, 1 1, 1 1, 1 1, 1 1)))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((1 1, 1 1, 1 1, 1 1, 1 1)))'))) +NULL +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(LINESTRING(0 0))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(LINESTRING(0 0))'))) +NULL +# If a polygon or multipolygon has counterclockwise internal rings, the rings are returned counterclockwise +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1))'))) +POLYGON((0 0,10 0,10 10,0 10,0 0),(1 1,1 2,2 2,2 1,1 1)) +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1)))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1)))'))) +MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(1 1,1 2,2 2,2 1,1 1))) +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1)))'))); +ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1)))'))) +GEOMETRYCOLLECTION(POLYGON((0 0,10 0,10 10,0 10,0 0),(1 1,1 2,2 2,2 1,1 1))) +# ST_VALIDATE raises an error if longitude is out of range +SELECT ST_VALIDATE(ST_GEOMFROMTEXT('POINT(0 270)', 4326)); +ERROR HY000: Longitude 270.000000 is out of range in function st_validate. It must be within (-180.000000, 180.000000]. +# ST_VALIDATE raises an error if latitude is out of range +SELECT ST_VALIDATE(ST_GEOMFROMTEXT('POINT(270 0)', 4326)); +ERROR HY000: Latitude 270.000000 is out of range in function st_validate. It must be within [-90.000000, 90.000000]. +# ST_VALIDATE returns the same geometry as it was given when it is valid +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))'))) AS +valid_polygon; +valid_polygon +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.25 0.75,0.75 0.75,0.75 0.25,0.25 0.25)) +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))',4053))) AS +valid_polygon; +valid_polygon +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.25 0.75,0.75 0.75,0.75 0.25,0.25 0.25)) +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))',2000))) AS +valid_polygon; +valid_polygon +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.25 0.75,0.75 0.75,0.75 0.25,0.25 0.25)) +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))',4326))) AS +valid_polygon; +valid_polygon +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.25 0.75,0.75 0.75,0.75 0.25,0.25 0.25)) +# ST_VALIDATE returns NULL if the geometry is invalid +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 1.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))'))) AS +should_be_null; +should_be_null +NULL +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 1.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))',4053))) AS +should_be_null; +should_be_null +NULL +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 1.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))',2000))) AS +should_be_null; +should_be_null +NULL +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 1.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))',4326))) AS +should_be_null; +should_be_null +NULL diff --git a/mysql-test/main/spatial_utility_function_validate.test b/mysql-test/main/spatial_utility_function_validate.test new file mode 100644 index 00000000000..77e439d8bfa --- /dev/null +++ b/mysql-test/main/spatial_utility_function_validate.test @@ -0,0 +1,101 @@ +# Copyright (c) 2014, Oracle and/or its affiliates +# Copyright (c) 2024, MariaDB Corporation. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA + + +############################################################################################ +# Creating the spatial objects # +############################################################################################ + +--echo # Creating the spatial Geometry object +USE test; + +--echo # ST_VALIDATE must return null when its parameter is NULL +SELECT ST_ASTEXT(ST_VALIDATE( NULL )); +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT(NULL,0))); +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT(NULL,4053))); + +--echo # ST_VALIDATE raises an error if the data is malformed +--error ER_GIS_INVALID_DATA +SELECT ST_VALIDATE( x'00000000DEADBEEF'); + +--echo # ST_VALIDATE return the input if it is valid +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POINT(15 25)'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOINT(5 0,25 0,15 10,15 25)'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING(10 15,20 15)'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING((25 0,0 15,15 30,0 5))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((5 0,7 10,0 15,10 15,15 25,20 15,30 15,22 10,25 0,15 5,5 0))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((0 0,0 10,10 10,0 0)),((10 10,10 15,15 15,10 10)))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(POINT(10 10),' + 'MULTIPOINT(0 0,10 10),' + 'LINESTRING(1 1,2 2,3 3),' + 'MULTILINESTRING((0 0,0 10,10 10,10 0),(10 10,10 15,15 15,10 10)))'))); + +--echo # The only valid empty geometry is the empty geometrycollection +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POINT()'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOINT()'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING()'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING(())'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON(())'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON((()),(()))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION()'))); + +--echo # Invalid geometries return null +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('LINESTRING(0 0,-0.00 0,0.0 0)'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTILINESTRING((0 0))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((0 0))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((1 1, 1 1, 1 1, 1 1, 1 1)))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(LINESTRING(0 0))'))); + +--echo # If a polygon or multipolygon has counterclockwise internal rings, the rings are returned counterclockwise +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1)))'))); +SELECT ST_ASTEXT(ST_VALIDATE(ST_GEOMFROMTEXT('GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 2 1, 2 2, 1 2, 1 1)))'))); + +--echo # ST_VALIDATE raises an error if longitude is out of range +--error ER_LONGITUDE_OUT_OF_RANGE +SELECT ST_VALIDATE(ST_GEOMFROMTEXT('POINT(0 270)', 4326)); + +--echo # ST_VALIDATE raises an error if latitude is out of range +--error ER_LATITUDE_OUT_OF_RANGE +SELECT ST_VALIDATE(ST_GEOMFROMTEXT('POINT(270 0)', 4326)); + +--echo # ST_VALIDATE returns the same geometry as it was given when it is valid +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))'))) AS +valid_polygon; +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))',4053))) AS +valid_polygon; +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))',2000))) AS +valid_polygon; +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))',4326))) AS +valid_polygon; + +--echo # ST_VALIDATE returns NULL if the geometry is invalid +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 1.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))'))) AS +should_be_null; +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 1.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))',4053))) AS +should_be_null; +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 1.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))',2000))) AS +should_be_null; +SELECT ST_ASTEXT(ST_VALIDATE( ST_GEOMFROMTEXT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 +0),( 0.25 0.25, 1.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))',4326))) AS +should_be_null; diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc index fa67fe92bed..5573ba4781b 100644 --- a/sql/item_geofunc.cc +++ b/sql/item_geofunc.cc @@ -2082,6 +2082,7 @@ mem_error: DBUG_RETURN(str_result); } + longlong Item_func_isvalid::val_int() { String *wkb= args[0]->val_str(&tmp); @@ -2106,6 +2107,73 @@ longlong Item_func_isvalid::val_int() return (longlong) valid; } + +String *Item_func_validate::val_str(String *str_value) +{ + DBUG_ENTER("Item_func_buffer::val_str"); + DBUG_ASSERT(fixed()); + String *wkb= args[0]->val_str(&tmp); + Geometry_buffer buffer; + Geometry *geometry; + int valid= 1; + str_value= NULL; + null_value= 1; + + if(args[0]->null_value) + DBUG_RETURN(str_value); + + if (!(geometry= Geometry::construct(&buffer, wkb->ptr(), wkb->length()))) + { + my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); + DBUG_RETURN(str_value); + } + + if (geometry->get_class_info()->m_type_id == Geometry::wkb_point) + { + double x, y; + if(((Gis_point *) geometry)->get_xy(&x, &y)) + DBUG_RETURN(str_value); + + if (x > MAX_LONGITUDE || x <= MIN_LONGITUDE) + { + my_error(ER_LATITUDE_OUT_OF_RANGE, MYF(0), x, "st_validate"); + DBUG_RETURN(str_value); + } + else if(y > MAX_LATITUDE || y < MIN_LATITUDE) { + my_error(ER_LONGITUDE_OUT_OF_RANGE, MYF(0), y,"st_validate"); + DBUG_RETURN(str_value); + } + + null_value= 0; + str_value= wkb; + DBUG_RETURN(str_value); + } + + if (geometry->is_valid(&valid)) + DBUG_RETURN(str_value); + + if (!valid) + DBUG_RETURN(str_value); + + if (geometry->get_class_info()->m_type_id == Geometry::wkb_polygon || + geometry->get_class_info()->m_type_id == Geometry::wkb_multipolygon || + geometry->get_class_info()->m_type_id == + Geometry::wkb_geometrycollection) + { + String clockwise_wkb; + if(geometry->make_clockwise(&clockwise_wkb)) + DBUG_RETURN(str_value); + + wkb->length(4); // keep the SRID + wkb->append(clockwise_wkb.ptr(), clockwise_wkb.length()); + } + + null_value= 0; + str_value= wkb; + DBUG_RETURN(str_value); +} + + bool Item_func_isempty::val_bool() { DBUG_ASSERT(fixed()); @@ -4299,37 +4367,21 @@ protected: virtual ~Create_func_isvalid() = default; }; -class Create_func_isvalid : public Create_func_arg1 +class Create_func_validate : public Create_func_arg1 { public: Item *create_1_arg(THD *thd, Item *arg1) override { - return new (thd->mem_root) Item_func_isvalid(thd, arg1); + return new (thd->mem_root) Item_func_validate(thd, arg1); } - static Create_func_isvalid s_singleton; + static Create_func_validate s_singleton; protected: - Create_func_isvalid() = default; - virtual ~Create_func_isvalid() = default; + Create_func_validate() = default; + virtual ~Create_func_validate() = default; }; -class Create_func_isvalid : public Create_func_arg1 -{ -public: - Item *create_1_arg(THD *thd, Item *arg1) override - { - return new (thd->mem_root) Item_func_isvalid(thd, arg1); - } - - static Create_func_isvalid s_singleton; - -protected: - Create_func_isvalid() = default; - virtual ~Create_func_isvalid() = default; -}; - - class Create_func_issimple : public Create_func_arg1 { public: @@ -4632,6 +4684,7 @@ Create_func_intersects Create_func_intersects::s_singleton; Create_func_isclosed Create_func_isclosed::s_singleton; Create_func_isempty Create_func_isempty::s_singleton; Create_func_isvalid Create_func_isvalid::s_singleton; +Create_func_validate Create_func_validate::s_singleton; Create_func_isring Create_func_isring::s_singleton; Create_func_issimple Create_func_issimple::s_singleton; Create_func_simplify Create_func_simplify::s_singleton; @@ -4703,6 +4756,7 @@ static Native_func_registry func_array_geom[] = { { STRING_WITH_LEN("ISCLOSED") }, GEOM_BUILDER(Create_func_isclosed)}, { { STRING_WITH_LEN("ISEMPTY") }, GEOM_BUILDER(Create_func_isempty)}, { { STRING_WITH_LEN("ISVALID") }, GEOM_BUILDER(Create_func_isvalid)}, + { { STRING_WITH_LEN("VALIDATE") }, GEOM_BUILDER(Create_func_validate)}, { { STRING_WITH_LEN("ISRING") }, GEOM_BUILDER(Create_func_isring)}, { { STRING_WITH_LEN("ISSIMPLE") }, GEOM_BUILDER(Create_func_issimple)}, { { STRING_WITH_LEN("SIMPLIFY") }, GEOM_BUILDER(Create_func_simplify)}, @@ -4787,6 +4841,7 @@ static Native_func_registry func_array_geom[] = { { STRING_WITH_LEN("ST_ISCLOSED") }, GEOM_BUILDER(Create_func_isclosed)}, { { STRING_WITH_LEN("ST_ISEMPTY") }, GEOM_BUILDER(Create_func_isempty)}, { { STRING_WITH_LEN("ST_ISVALID") }, GEOM_BUILDER(Create_func_isvalid)}, + { { STRING_WITH_LEN("ST_VALIDATE") }, GEOM_BUILDER(Create_func_validate)}, { { STRING_WITH_LEN("ST_ISRING") }, GEOM_BUILDER(Create_func_isring)}, { { STRING_WITH_LEN("ST_ISSIMPLE") }, GEOM_BUILDER(Create_func_issimple)}, { { STRING_WITH_LEN("ST_SIMPLIFY") }, GEOM_BUILDER(Create_func_simplify)}, diff --git a/sql/item_geofunc.h b/sql/item_geofunc.h index e065a5c3fe4..c79d55b3dd4 100644 --- a/sql/item_geofunc.h +++ b/sql/item_geofunc.h @@ -1044,6 +1044,26 @@ public: { return get_item_copy(thd, this); } }; +class Item_func_validate: public Item_geometry_func_args_geometry +{ +public: + String tmp; + Item_func_validate(THD *thd, Item *a): + Item_geometry_func_args_geometry(thd, a) {} + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("st_validate") }; + return name; + } + String *val_str(String *) override; + const Type_handler *type_handler() const override + { + return &type_handler_point; + } + Item *get_copy(THD *thd) override + { return get_item_copy(thd, this); } +}; + class Item_func_dimension: public Item_long_func_args_geometry { public: diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 4c2d24f1492..97dce92dc47 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12305,3 +12305,7 @@ ER_INCOMPATIBLE_EVENT_FLAG eng "Event flag '%s' in the condition expression is not compatible with the trigger event type '%s'" ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS eng "%s has not been implemented for geographic spatial reference systems." +ER_LONGITUDE_OUT_OF_RANGE + eng "Longitude %f is out of range in function %s. It must be within (-180.000000, 180.000000]." +ER_LATITUDE_OUT_OF_RANGE + eng "Latitude %f is out of range in function %s. It must be within [-90.000000, 90.000000]." diff --git a/sql/spatial.cc b/sql/spatial.cc index 08cc9e41ef0..f924059a35d 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -1739,6 +1739,59 @@ int Gis_line_string::store_shapes(Gcalc_shape_transporter *trn) const return trn->complete_line(); } + +/* + Calculate the internal area using the shoelace formula + (https://en.wikipedia.org/wiki/Shoelace_formula). If the area is < 0 then + it is clockwise. If the area is > 0 it is counterclockwise. + If it is 0 is degenerate. +*/ +int Gis_line_string::is_clockwise(int *result) const +{ + uint32 num_points; + double area= 0; + + if (this->num_points(&num_points)) + return 1; + + for (uint32 i= 1; i <= num_points; i++) + { + Geometry_buffer buffer_first, buffer_second; + Geometry *point_first, *point_second; + String wkb_first, wkb_second; + + if (wkb_first.reserve(SRID_SIZE + WKB_HEADER_SIZE) || + wkb_second.reserve(SRID_SIZE + WKB_HEADER_SIZE)) + return 1; + + wkb_first.q_append(SRID_PLACEHOLDER); + wkb_second.q_append(SRID_PLACEHOLDER); + + if (this->point_n(i, &wkb_first) || + this->point_n((i == num_points) ? 1 : i + 1, &wkb_second)) + return 1; + + if (!(point_first= + Geometry::construct(&buffer_first, wkb_first.ptr(), + wkb_first.length())) || + !(point_second= + Geometry::construct(&buffer_second, wkb_second.ptr(), + wkb_second.length()))) + return 1; + + double x1, x2, y1, y2; + if (((Gis_point *) point_first)->get_xy(&x1, &y1) || + ((Gis_point *) point_second)->get_xy(&x2, &y2)) + return 1; + + area+= (x1 * y2) - (x2 * y1); + } + + *result= (area < 0); + return 0; +} + + const Geometry::Class_info *Gis_line_string::get_class_info() const { return &linestring_class; @@ -2444,6 +2497,68 @@ single_point_ring: } +int Gis_polygon::make_clockwise(String *result) const +{ + String ring_wkb= 0; + uint32 num_interior_ring; + Geometry *ring; + Geometry_buffer buffer; + int is_clockwise; + uint32 ring_points; + + if(ring_wkb.reserve(SRID_SIZE + WKB_HEADER_SIZE) || + result->reserve(SRID_SIZE + WKB_HEADER_SIZE)) + return 1; + + if (this->num_interior_ring(&num_interior_ring) || + this->exterior_ring(&ring_wkb)) + return 1; + + result->length(0); + result->append((char) wkb_ndr); + result->q_append((uint32) wkb_polygon); + result->q_append((uint32) num_interior_ring + 1); + result->append(ring_wkb.ptr() + WKB_HEADER_SIZE, + ring_wkb.length() - WKB_HEADER_SIZE); + + for(uint32 i= 1; i <= num_interior_ring; i++) + { + ring_wkb.length(0); + ring_wkb.q_append(SRID_PLACEHOLDER); + if (this->interior_ring_n(i, &ring_wkb)) + return 1; + + if (!(ring= Geometry::construct(&buffer, ring_wkb.ptr(), + ring_wkb.length()))) + return 1; + + if (ring->is_clockwise(&is_clockwise)) + return 1; + + if (is_clockwise) + { + result->append(ring_wkb.ptr() + WKB_HEADER_SIZE + SRID_SIZE, + ring_wkb.length() - (WKB_HEADER_SIZE + SRID_SIZE)); + continue; + } + + if (ring->num_points(&ring_points)) + return 1; + result->q_append((uint32) ring_points); + + for (uint32 i= ring_points; i > 0; i--) + { + String point= 0; + ring->point_n(i, &point); + result->append(point.ptr() + WKB_HEADER_SIZE, + point.length() - WKB_HEADER_SIZE); + } + } + + return 0; +} + + const Geometry::Class_info *Gis_polygon::get_class_info() const { return &polygon_class; @@ -3777,6 +3892,43 @@ int Gis_multi_polygon::store_shapes(Gcalc_shape_transporter *trn) const } +int Gis_multi_polygon::make_clockwise(String *result) const +{ + Geometry_buffer buffer; + uint32 num_polygons; + Geometry *polygon; + + if(this->num_geometries(&num_polygons) || + result->reserve(SRID_SIZE + WKB_HEADER_SIZE)) + return 1; + + result->q_append((char) wkb_ndr); + result->q_append((uint32) wkb_multipolygon); + result->q_append((uint32) num_polygons); + for (uint32 i= 1; i <= num_polygons; i++) + { + String wkb= 0, clockwise_wkb= 0; + if (wkb.reserve(SRID_SIZE + BYTE_ORDER_SIZE + WKB_HEADER_SIZE)) + return 0; + + wkb.q_append(SRID_PLACEHOLDER); + if (this->geometry_n(i, &wkb) || + !(polygon= Geometry::construct(&buffer, wkb.ptr(), wkb.length()))) + return 1; + + if(polygon->make_clockwise(&clockwise_wkb)) + return 1; + + result->q_append((char) wkb_ndr); + result->q_append((uint32) wkb_polygon); + result->append(clockwise_wkb.ptr() + WKB_HEADER_SIZE, + clockwise_wkb.length() - WKB_HEADER_SIZE); + } + + return 0; +} + + const Geometry::Class_info *Gis_multi_polygon::get_class_info() const { return &multipolygon_class; @@ -4428,6 +4580,53 @@ int Gis_geometry_collection::store_shapes(Gcalc_shape_transporter *trn) const } +int Gis_geometry_collection::make_clockwise(String *result) const +{ + Geometry_buffer buffer; + uint32 num_geometries; + Geometry *geometry; + + if(this->num_geometries(&num_geometries) || + result->reserve(SRID_SIZE + WKB_HEADER_SIZE)) + return 1; + + result->q_append((char) wkb_ndr); + result->q_append((uint32) wkb_geometrycollection); + result->q_append((uint32) num_geometries); + for (uint32 i= 1; i <= num_geometries; i++) + { + String wkb= 0, clockwise_wkb= 0; + if (wkb.reserve(SRID_SIZE + BYTE_ORDER_SIZE + WKB_HEADER_SIZE)) + return 0; + + wkb.q_append(SRID_PLACEHOLDER); + if (this->geometry_n(i, &wkb) || + !(geometry= Geometry::construct(&buffer, wkb.ptr(), wkb.length()))) + return 1; + + result->q_append((char) wkb_ndr); + result->q_append((uint32) geometry->get_class_info()->m_type_id); + if (geometry->get_class_info()->m_type_id == Geometry::wkb_polygon || + geometry->get_class_info()->m_type_id == Geometry::wkb_multipolygon || + geometry->get_class_info()->m_type_id == + Geometry::wkb_geometrycollection) + { + if(geometry->make_clockwise(&clockwise_wkb)) + return 1; + result->append(clockwise_wkb.ptr() + WKB_HEADER_SIZE, + clockwise_wkb.length() - WKB_HEADER_SIZE); + } + else + { + result->append(wkb.ptr() + SRID_SIZE + WKB_HEADER_SIZE, + wkb.length() - (SRID_SIZE + WKB_HEADER_SIZE)); + } + } + + return 0; +} + + const Geometry::Class_info *Gis_geometry_collection::get_class_info() const { return &geometrycollection_class; diff --git a/sql/spatial.h b/sql/spatial.h index 9e92d3e3be5..9ea47230efe 100644 --- a/sql/spatial.h +++ b/sql/spatial.h @@ -305,7 +305,8 @@ public: virtual int interior_ring_n(uint32 num, String *result) const { return -1; } virtual int geometry_n(uint32 num, String *result) const { return -1; } virtual int store_shapes(Gcalc_shape_transporter *trn) const=0; - + virtual int is_clockwise(int *result) const { return -1; } + virtual int make_clockwise(String *result) const{ return -1; } public: static Geometry *create_by_typeid(Geometry_buffer *buffer, int type_id); @@ -498,6 +499,7 @@ public: return 0; } int store_shapes(Gcalc_shape_transporter *trn) const override; + int is_clockwise(int *result) const override; const Class_info *get_class_info() const override; }; @@ -533,6 +535,7 @@ public: return 0; } int store_shapes(Gcalc_shape_transporter *trn) const override; + int make_clockwise(String *result) const override; const Class_info *get_class_info() const override; }; @@ -634,6 +637,7 @@ public: return 0; } int store_shapes(Gcalc_shape_transporter *trn) const override; + 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; }; @@ -663,6 +667,7 @@ public: int geometry_n(uint32 num, String *result) const override; bool dimension(uint32 *dim, const char **end) const override; int store_shapes(Gcalc_shape_transporter *trn) const override; + int make_clockwise(String *result) const override; const Class_info *get_class_info() const override; };