/* Copyright (c) 2003, 2021, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. 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, version 2.0, 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, 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ /** @file @brief This file defines all spatial functions */ #include "item_geofunc.h" #include "gstream.h" // Gis_read_stream #include "sql_class.h" // THD #include "gis_bg_traits.h" #include "parse_tree_helpers.h" #include "item_geofunc_internal.h" #include static int check_geometry_valid(Geometry *geom); /** Check whether all points in the sequence container are colinear. @param ls a sequence of points with no duplicates. @return true if the points are colinear, false otherwise. */ template bool is_colinear(const Point_range &ls) { if (ls.size() < 3) return true; double x1, x2, x3, y1, y2, y3, X1, X2, Y1, Y2; for (size_t i= 0; i < ls.size() - 2; i++) { x1= ls[i].template get<0>(); x2= ls[i + 1].template get<0>(); x3= ls[i + 2].template get<0>(); y1= ls[i].template get<1>(); y2= ls[i + 1].template get<1>(); y3= ls[i + 2].template get<1>(); X1= x2 - x1; X2= x3 - x2; Y1= y2 - y1; Y2= y3 - y2; if (X1 * Y2 - X2 * Y1 != 0) return false; } return true; } Item_geometry_func::Item_geometry_func(const POS &pos, PT_item_list *list) :Item_str_func(pos, list) {} Field *Item_geometry_func::tmp_table_field(TABLE *t_arg) { Field *result; if ((result= new Field_geom(max_length, maybe_null, item_name.ptr(), t_arg->s, get_geometry_type()))) result->init(t_arg); return result; } void Item_geometry_func::fix_length_and_dec() { collation.set(&my_charset_bin); decimals=0; max_length= 0xFFFFFFFFU; maybe_null= 1; } bool Item_func_geometry_from_text::itemize(Parse_context *pc, Item **res) { if (skip_itemize(res)) return false; if (super::itemize(pc, res)) return true; assert(arg_count == 1 || arg_count == 2); if (arg_count == 1) pc->thd->lex->set_uncacheable(pc->select, UNCACHEABLE_RAND); return false; } /** Parses a WKT string to produce a geometry encoded with an SRID prepending its WKB bytes, namely a byte string of GEOMETRY format. @param str buffer to hold result, may not be filled. @return the buffer that hold the GEOMETRY byte string result, may or may not be the same as 'str' parameter. */ String *Item_func_geometry_from_text::val_str(String *str) { assert(fixed == 1); Geometry_buffer buffer; String arg_val; String *wkt= args[0]->val_str_ascii(&arg_val); if ((null_value= (!wkt || args[0]->null_value))) return 0; Gis_read_stream trs(wkt->charset(), wkt->ptr(), wkt->length()); uint32 srid= 0; if (arg_count == 2) { if ((null_value= args[1]->null_value)) { assert(maybe_null); return NULL; } else srid= (uint32)args[1]->val_int(); } str->set_charset(&my_charset_bin); if ((null_value= str->reserve(GEOM_HEADER_SIZE, 512))) return 0; str->length(0); str->q_append(srid); if (!Geometry::create_from_wkt(&buffer, &trs, str, 0)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } return str; } bool Item_func_geometry_from_wkb::itemize(Parse_context *pc, Item **res) { if (skip_itemize(res)) return false; if (super::itemize(pc, res)) return true; assert(arg_count == 1 || arg_count == 2); if (arg_count == 1) pc->thd->lex->set_uncacheable(pc->select, UNCACHEABLE_RAND); return false; } /** Parses a WKT string to produce a geometry encoded with an SRID prepending its WKB bytes, namely a byte string of GEOMETRY format. @param str buffer to hold result, may not be filled. @return the buffer that hold the GEOMETRY byte string result, may or may not be the same as 'str' parameter. */ String *Item_func_geometry_from_wkb::val_str(String *str) { assert(fixed == 1); String *wkb= NULL; uint32 srid= 0; if (arg_count == 2) { srid= static_cast(args[1]->val_int()); if ((null_value= args[1]->null_value)) return NULL; } wkb= args[0]->val_str(&tmp_value); if ((null_value= (!wkb || args[0]->null_value))) return NULL; /* GeometryFromWKB(wkb [,srid]) understands both WKB (without SRID) and Geometry (with SRID) values in the "wkb" argument. In case if a Geometry type value is passed, we assume that the value is well-formed and can directly return it without going through Geometry::create_from_wkb(), and consequently such WKB data must be MySQL standard (little) endian. Note that users can pass via client any WKB/Geometry byte string, including those of big endianess. */ if (args[0]->field_type() == MYSQL_TYPE_GEOMETRY) { if (arg_count == 1) { push_warning_printf(current_thd, Sql_condition::SL_WARNING, ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO, ER_THD(current_thd, ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO), func_name(), func_name()); } else if (arg_count == 2) { push_warning_printf(current_thd, Sql_condition::SL_WARNING, ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID, ER_THD(current_thd, ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID), func_name(), func_name()); } Geometry_buffer buff; if (Geometry::construct(&buff, wkb->ptr(), wkb->length()) == NULL) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } /* Check if SRID embedded into the Geometry value differs from the SRID value passed in the second argument. */ if (srid == uint4korr(wkb->ptr())) return wkb; // Do not differ /* Replace SRID to the one passed in the second argument. Note, we cannot replace SRID directly in wkb->ptr(), because wkb can point to some value that we should not touch, e.g. to a SP variable value. So we need to copy to "str". */ if ((null_value= str->copy(*wkb))) return NULL; str->write_at_position(0, srid); return str; } str->set_charset(&my_charset_bin); if (str->reserve(GEOM_HEADER_SIZE, 512)) { null_value= true; /* purecov: inspected */ return NULL; /* purecov: inspected */ } str->length(0); str->q_append(srid); Geometry_buffer buffer; if (!Geometry::create_from_wkb(&buffer, wkb->ptr(), wkb->length(), str, false/* Don't init stream. */)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } return str; } /** Definition of various string constants used for writing and reading GeoJSON data. */ const char *Item_func_geomfromgeojson::TYPE_MEMBER= "type"; const char *Item_func_geomfromgeojson::CRS_MEMBER= "crs"; const char *Item_func_geomfromgeojson::GEOMETRY_MEMBER= "geometry"; const char *Item_func_geomfromgeojson::PROPERTIES_MEMBER= "properties"; const char *Item_func_geomfromgeojson::FEATURES_MEMBER= "features"; const char *Item_func_geomfromgeojson::GEOMETRIES_MEMBER= "geometries"; const char *Item_func_geomfromgeojson::COORDINATES_MEMBER= "coordinates"; const char *Item_func_geomfromgeojson::CRS_NAME_MEMBER= "name"; const char *Item_func_geomfromgeojson::NAMED_CRS= "name"; const char *Item_func_geomfromgeojson::SHORT_EPSG_PREFIX= "EPSG:"; const char *Item_func_geomfromgeojson::POINT_TYPE= "Point"; const char *Item_func_geomfromgeojson::MULTIPOINT_TYPE= "MultiPoint"; const char *Item_func_geomfromgeojson::LINESTRING_TYPE= "LineString"; const char *Item_func_geomfromgeojson::MULTILINESTRING_TYPE= "MultiLineString"; const char *Item_func_geomfromgeojson::POLYGON_TYPE= "Polygon"; const char *Item_func_geomfromgeojson::MULTIPOLYGON_TYPE= "MultiPolygon"; const char *Item_func_geomfromgeojson::FEATURE_TYPE= "Feature"; const char *Item_func_geomfromgeojson:: FEATURECOLLECTION_TYPE= "FeatureCollection"; const char *Item_func_geomfromgeojson:: LONG_EPSG_PREFIX= "urn:ogc:def:crs:EPSG::"; const char *Item_func_geomfromgeojson:: CRS84_URN= "urn:ogc:def:crs:OGC:1.3:CRS84"; const char *Item_func_geomfromgeojson:: GEOMETRYCOLLECTION_TYPE= "GeometryCollection"; /** = ST_GEOMFROMGEOJSON([, [, ]]) Takes a GeoJSON input string and outputs a GEOMETRY. This function supports both single GeoJSON objects and geometry collections. In addition, feature objects and feature collections are supported (feature collections are translated into GEOMETRYCOLLECTION). It follows the standard described at http://geojson.org/geojson-spec.html (revision 1.0). */ String *Item_func_geomfromgeojson::val_str(String *buf) { if (arg_count > 1) { // Check and parse the OPTIONS parameter. longlong dimension_argument= args[1]->val_int(); if ((null_value= args[1]->null_value)) return NULL; if (dimension_argument == 1) { m_handle_coordinate_dimension= Item_func_geomfromgeojson::reject_document; } else if (dimension_argument == 2) { m_handle_coordinate_dimension= Item_func_geomfromgeojson::strip_now_accept_future; } else if (dimension_argument == 3) { m_handle_coordinate_dimension= Item_func_geomfromgeojson::strip_now_reject_future; } else if (dimension_argument == 4) { m_handle_coordinate_dimension= Item_func_geomfromgeojson::strip_now_strip_future; } else { char option_string[MAX_BIGINT_WIDTH + 1]; if (args[1]->unsigned_flag) ullstr(dimension_argument, option_string); else llstr(dimension_argument, option_string); my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "option", option_string, func_name()); return error_str(); } } if (arg_count > 2) { /* Check and parse the SRID parameter. If this is set to a valid value, any CRS member in the GeoJSON document will be ignored. */ longlong srid_argument= args[2]->val_int(); if ((null_value= args[2]->null_value)) return NULL; // Only allow unsigned 32 bits integer as SRID. if (srid_argument < 0 || srid_argument > UINT_MAX32) { char srid_string[MAX_BIGINT_WIDTH + 1]; if (args[2]->unsigned_flag) ullstr(srid_argument, srid_string); else llstr(srid_argument, srid_string); my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "SRID", srid_string, func_name()); return error_str(); } else { m_user_srid= static_cast(srid_argument); m_user_provided_srid= true; } } Json_wrapper wr; if (get_json_wrapper(args, 0, buf, func_name(), &wr, true)) return error_str(); /* We will handle JSON NULL the same way as we handle SQL NULL. The reason behind this is that we want the following SQL to return SQL NULL: SELECT ST_GeomFromGeoJSON( JSON_EXTRACT( '{ "type": "Feature", "geometry": null, "properties": { "name": "Foo" } }', '$.geometry') ); The function JSON_EXTRACT will return a JSON NULL, so if we don't handle JSON NULL as SQL NULL the above SQL will raise an error since we would expect a SQL NULL or a JSON object. */ null_value= (args[0]->null_value || wr.type() == Json_dom::J_NULL); if (null_value) { assert(maybe_null); return NULL; } if (wr.type() != Json_dom::J_OBJECT) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return error_str(); } const Json_object *root_obj= down_cast(wr.to_dom()); /* Set the default SRID to 4326. This will be overwritten if a valid CRS is found in the GeoJSON input, or if the user has specified a SRID as an argument. It would probably be smart to allocate a percentage of the length of the input string (something like buf->realloc(json_string->length() * 0.2)). This would save a lot of reallocations and boost performance, especially for large inputs. But it is difficult to predict how much of the json input that will be parsed into output data. */ if (buf->reserve(GEOM_HEADER_SIZE, 512)) { my_error(ER_OUTOFMEMORY, GEOM_HEADER_SIZE); return error_str(); } buf->set_charset(&my_charset_bin); buf->length(0); buf->q_append(static_cast(4326)); /* The rollback variable is used for detecting/accepting NULL objects inside collections (a feature with NULL geometry is allowed, and thus we can have a geometry collection with a NULL geometry translated into following WKT: GEOMETRYCOLLECTION()). parse_object() does a recursive parsing of the GeoJSON document. */ String collection_buffer; bool rollback= false; Geometry *result_geometry= NULL; m_srid_found_in_document = -1; if (parse_object(root_obj, &rollback, &collection_buffer, false, &result_geometry)) { // Do a delete here, to be sure that we have no memory leaks. delete result_geometry; result_geometry= NULL; if (rollback) { assert(maybe_null); null_value= true; return NULL; } return error_str(); } // Set the correct SRID for the geometry data. if (m_user_provided_srid) buf->write_at_position(0, m_user_srid); else if (m_srid_found_in_document > -1) buf->write_at_position(0, static_cast(m_srid_found_in_document)); bool return_result= result_geometry->as_wkb(buf, false); delete result_geometry; result_geometry= NULL; if (return_result) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } return buf; } /** Case insensitive lookup of a member in a JSON object. This is needed since the get()-method of the JSON object is case sensitive. @param v The object to look for the member in. @param member_name Name of the member to look after @return The member if one was found, NULL otherwise. */ const Json_dom *Item_func_geomfromgeojson:: my_find_member_ncase(const Json_object *object, const char *member_name) { Json_object::const_iterator itr; for (itr= object->begin(); itr != object->end(); ++itr) { if (native_strcasecmp(member_name, itr->first.c_str()) == 0) return itr->second; } return NULL; } /** Takes a JSON object as input, and parses the data to a Geometry object. The call stack will be no larger than the maximum depth of the GeoJSON document, which is more or less equivalent to the number of nested collections in the document. @param object A JSON object object to parse. @param rollback Pointer to a boolean indicating if parsed data should be reverted/rolled back. @param buffer A string buffer to be used by GeometryCollection @param is_parent_featurecollection Indicating if the current geometry is a child of a FeatureCollection. @param[out] geometry A pointer to the parsed geometry. @return true if the parsing failed, false otherwise. Note that if rollback is set to true and true is returned, the parsing succeeded, but no Geometry data could be parsed. */ bool Item_func_geomfromgeojson:: parse_object(const Json_object *object, bool *rollback, String *buffer, bool is_parent_featurecollection, Geometry **geometry) { /* A GeoJSON object MUST have a type member, which MUST be of string type. */ const Json_dom *type_member= my_find_member_ncase(object, TYPE_MEMBER); if (!is_member_valid(type_member, TYPE_MEMBER, Json_dom::J_STRING, false, NULL)) { return true; } /* Check if this object has a CRS member. We allow the CRS member to be JSON NULL. */ const Json_dom *crs_member= my_find_member_ncase(object, CRS_MEMBER); if (crs_member != NULL) { if (crs_member->json_type() == Json_dom::J_OBJECT) { const Json_object *crs_obj= down_cast(crs_member); if (parse_crs_object(crs_obj)) return true; } else if (crs_member->json_type() != Json_dom::J_NULL) { my_error(ER_INVALID_GEOJSON_WRONG_TYPE, MYF(0), func_name(), CRS_MEMBER, "object"); return true; } } // Handle feature objects and feature collection objects. const Json_string *type_member_str= down_cast(type_member); if (strcmp(type_member_str->value().c_str(), FEATURE_TYPE) == 0) { /* Check if this feature object has the required "geometry" and "properties" member. Note that we do not use the member "properties" for anything else than checking for valid GeoJSON document. */ bool dummy; const Json_dom *geometry_member= my_find_member_ncase(object, GEOMETRY_MEMBER); const Json_dom *properties_member= my_find_member_ncase(object, PROPERTIES_MEMBER); if (!is_member_valid(geometry_member, GEOMETRY_MEMBER, Json_dom::J_OBJECT, true, rollback) || !is_member_valid(properties_member, PROPERTIES_MEMBER, Json_dom::J_OBJECT, true, &dummy) || *rollback) { return true; } const Json_object *geometry_member_obj= down_cast(geometry_member); return parse_object(geometry_member_obj, rollback, buffer, false, geometry); } else if (strcmp(type_member_str->value().c_str(), FEATURECOLLECTION_TYPE) == 0) { // FeatureCollections cannot be nested according to GeoJSON spec. if (is_parent_featurecollection) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } // We will handle a FeatureCollection as a GeometryCollection. const Json_dom *features= my_find_member_ncase(object, FEATURES_MEMBER); if (!is_member_valid(features, FEATURES_MEMBER, Json_dom::J_ARRAY, false, NULL)) { return true; } const Json_array *features_array= down_cast(features); return parse_object_array(features_array, Geometry::wkb_geometrycollection, rollback, buffer, true, geometry); } else { Geometry::wkbType wkbtype= get_wkbtype(type_member_str->value().c_str()); if (wkbtype == Geometry::wkb_invalid_type) { // An invalid GeoJSON type was found. my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } else { /* All objects except GeometryCollection MUST have a member "coordinates" of type array. GeometryCollection MUST have a member "geometries" of type array. */ const char *member_name; if (wkbtype == Geometry::wkb_geometrycollection) member_name= GEOMETRIES_MEMBER; else member_name= COORDINATES_MEMBER; const Json_dom *array_member= my_find_member_ncase(object, member_name); if (!is_member_valid(array_member, member_name, Json_dom::J_ARRAY, false, NULL)) { return true; } const Json_array *array_member_array= down_cast(array_member); return parse_object_array(array_member_array, wkbtype, rollback, buffer, false, geometry); } } // Defensive code. This should never be reached. /* purecov: begin inspected */ assert(false); return true; /* purecov: end inspected */ } /** Parse an array of coordinates to a Gis_point. Parses an array of coordinates to a Gis_point. This function must handle according to the handle_dimension parameter on how non 2D objects should be handled. According to the specification, a position array must have at least two elements, but there is no upper limit. @param coordinates A JSON array of coordinates. @param[out] point A pointer to the parsed Gis_point. @return true if the parsing failed, false otherwise. */ bool Item_func_geomfromgeojson:: get_positions(const Json_array *coordinates, Gis_point *point) { /* According to GeoJSON specification, a position array must have at least two positions. */ if (coordinates->size() < 2) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } switch (m_handle_coordinate_dimension) { case Item_func_geomfromgeojson::reject_document: if (coordinates->size() > GEOM_DIM) { my_error(ER_DIMENSION_UNSUPPORTED, MYF(0), func_name(), coordinates->size(), GEOM_DIM); return true; } break; case Item_func_geomfromgeojson::strip_now_reject_future: /* The version in development as of writing, only supports 2 dimensions. When dimension count is increased beyond 2, we want the function to fail. */ if (GEOM_DIM > 2 && coordinates->size() > 2) { my_error(ER_DIMENSION_UNSUPPORTED, MYF(0), func_name(), coordinates->size(), GEOM_DIM); return true; } break; case Item_func_geomfromgeojson::strip_now_strip_future: case Item_func_geomfromgeojson::strip_now_accept_future: if (GEOM_DIM > 2) assert(false); break; default: // Unspecified behaviour. assert(false); return true; } // Check if all array members are numbers. for (size_t i= 0; i < coordinates->size(); ++i) { if (!(*coordinates)[i]->is_number()) { my_error(ER_INVALID_GEOJSON_WRONG_TYPE, MYF(0), func_name(), "array coordinate", "number"); return true; } /* Even though we only need the two first coordinates, we check the rest of them to ensure that the GeoJSON is valid. Remember to call set_alias(), so that this wrapper does not take ownership of the data. */ Json_wrapper coord((*coordinates)[i]); coord.set_alias(); if (i == 0) point->set<0>(coord.coerce_real("")); else if (i == 1) point->set<1>(coord.coerce_real("")); } return false; } /** Takes a JSON array as input, does a recursive parsing and returns a Geometry object. This function differs from parse_object() in that it takes an array as input instead of a object. This is one of the members "coordinates" or "geometries" of a GeoJSON object. @param data_array A JSON array to parse. @param type The type of the GeoJSON object this array belongs to. @param rollback Pointer to a boolean indicating if parsed data should be reverted/rolled back. @param buffer A String buffer to be used by GeometryCollection. @param[out] geometry A pointer to the parsed Geometry. @return true on failure, false otherwise. */ bool Item_func_geomfromgeojson:: parse_object_array(const Json_array *data_array, Geometry::wkbType type, bool *rollback, String *buffer, bool is_parent_featurecollection, Geometry **geometry) { switch (type) { case Geometry::wkb_geometrycollection: { /* Ensure that the provided buffer is empty, and then create a empty GeometryCollection using this buffer. */ buffer->set_charset(&my_charset_bin); buffer->length(0); buffer->reserve(GEOM_HEADER_SIZE + SIZEOF_INT); write_geometry_header(buffer, 0, Geometry::wkb_geometrycollection, 0); Gis_geometry_collection *collection= new Gis_geometry_collection(); *geometry= collection; collection->set_data_ptr(buffer->ptr() + GEOM_HEADER_SIZE, 4); collection->has_geom_header_space(true); for (size_t i= 0; i < data_array->size(); ++i) { if ((*data_array)[i]->json_type() != Json_dom::J_OBJECT) { my_error(ER_INVALID_GEOJSON_WRONG_TYPE, MYF(0), func_name(), GEOMETRIES_MEMBER, "object array"); return true; } const Json_object *object= down_cast((*data_array)[i]); String geo_buffer; Geometry *parsed_geometry= NULL; if (parse_object(object, rollback, &geo_buffer, is_parent_featurecollection, &parsed_geometry)) { /* This will happen if a feature object contains a NULL geometry object (which is a perfectly valid GeoJSON object). */ if (*rollback) { *rollback= false; } else { delete parsed_geometry; parsed_geometry= NULL; return true; } } else { if (parsed_geometry->get_geotype() == Geometry::wkb_polygon) { // Make the Gis_polygon suitable for MySQL GIS code. Gis_polygon *polygon= static_cast(parsed_geometry); polygon->to_wkb_unparsed(); } collection->append_geometry(parsed_geometry, buffer); } delete parsed_geometry; parsed_geometry= NULL; } return false; } case Geometry::wkb_point: { Gis_point *point= new Gis_point(false); *geometry= point; return get_positions(data_array, point); } case Geometry::wkb_linestring: { Gis_line_string *linestring= new Gis_line_string(false); *geometry= linestring; if (get_linestring(data_array, linestring)) return true; return false; } case Geometry::wkb_multipoint: { // Ensure that the MultiPoint has at least one Point. if (data_array->size() == 0) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } Gis_multi_point *multipoint= new Gis_multi_point(false); *geometry= multipoint; for (size_t i= 0; i < data_array->size(); ++i) { if ((*data_array)[i]->json_type() != Json_dom::J_ARRAY) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } else { const Json_array *coords= down_cast((*data_array)[i]); Gis_point point; if (get_positions(coords, &point)) return true; multipoint->push_back(point); } } return false; } case Geometry::wkb_multilinestring: { // Ensure that the MultiLineString has at least one LineString. if (data_array->size() == 0) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } Gis_multi_line_string *multilinestring= new Gis_multi_line_string(false); *geometry= multilinestring; for (size_t i= 0; i < data_array->size(); ++i) { if ((*data_array)[i]->json_type() != Json_dom::J_ARRAY) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } const Json_array *coords= down_cast((*data_array)[i]); Gis_line_string linestring; if (get_linestring(coords, &linestring)) return true; multilinestring->push_back(linestring); } return false; } case Geometry::wkb_polygon: { Gis_polygon *polygon= new Gis_polygon(false); *geometry= polygon; return get_polygon(data_array, polygon); } case Geometry::wkb_multipolygon: { // Ensure that the MultiPolygon has at least one Polygon. if (data_array->size() == 0) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } Gis_multi_polygon *multipolygon= new Gis_multi_polygon(false); *geometry= multipolygon; for (size_t i= 0; i < data_array->size(); ++i) { if ((*data_array)[i]->json_type() != Json_dom::J_ARRAY) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } const Json_array *coords= down_cast((*data_array)[i]); Gis_polygon polygon; if (get_polygon(coords, &polygon)) return true; multipolygon->push_back(polygon); } return false; } default: { assert(false); return false; } } } /** Create a Gis_line_string from a JSON array. @param data_array A JSON array containing the coordinates. @param linestring Pointer to a linestring to be filled with data. @return true on failure, false otherwise. */ bool Item_func_geomfromgeojson::get_linestring(const Json_array *data_array, Gis_line_string *linestring) { // Ensure that the linestring has at least one point. if (data_array->size() < 2) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } for (size_t i= 0; i < data_array->size(); ++i) { if ((*data_array)[i]->json_type() != Json_dom::J_ARRAY) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } else { Gis_point point; const Json_array *coords= down_cast((*data_array)[i]); if (get_positions(coords, &point)) return true; linestring->push_back(point); } } return false; } /** Create a Gis_polygon from a JSON array. @param data_array A JSON array containing the coordinates. @param polygon A pointer to a Polygon to be filled with data. @return true on failure, false otherwise. */ bool Item_func_geomfromgeojson::get_polygon(const Json_array *data_array, Gis_polygon *polygon) { // Ensure that the Polygon has at least one ring. if (data_array->size() == 0) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } for (size_t ring_count= 0; ring_count < data_array->size(); ++ring_count) { // Polygon rings must have at least four points, according to GeoJSON spec. if ((*data_array)[ring_count]->json_type() != Json_dom::J_ARRAY) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } const Json_array *polygon_ring= down_cast((*data_array)[ring_count]); if (polygon_ring->size() < 4) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } polygon->inners().resize(ring_count); for (size_t i= 0; i < polygon_ring->size(); ++i) { if ((*polygon_ring)[i]->json_type() != Json_dom::J_ARRAY) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } Gis_point point; const Json_array *coords= down_cast((*polygon_ring)[i]); if (get_positions(coords, &point)) return true; if (ring_count == 0) polygon->outer().push_back(point); else polygon->inners()[ring_count - 1].push_back(point); } // Check if the ring is closed, which is must be according to GeoJSON spec. Gis_point first; Gis_point last; if (ring_count == 0) { first= polygon->outer()[0]; last= polygon->outer().back(); } else { first= polygon->inners()[ring_count - 1][0]; last= polygon->inners()[ring_count - 1].back(); } if (!(first == last)) { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } } return false; } /** Converts GeoJSON type string to a wkbType. Convert a string from a "type" member in GeoJSON to its equivalent Geometry enumeration. The type names are case sensitive as stated in the specification: The value of the type member must be one of: "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", "GeometryCollection", "Feature", or "FeatureCollection". The case of the type member values must be as shown here. Note that even though Feature and FeatureCollection are added here, these types will be handled before this function is called (in parse_object()). @param typestring A GeoJSON type string. @return The corresponding wkbType, or wkb_invalid_type if no matching type was found. */ Geometry::wkbType Item_func_geomfromgeojson::get_wkbtype(const char *typestring) { if (strcmp(typestring, POINT_TYPE) == 0) return Geometry::wkb_point; else if (strcmp(typestring, MULTIPOINT_TYPE) == 0) return Geometry::wkb_multipoint; else if (strcmp(typestring, LINESTRING_TYPE) == 0) return Geometry::wkb_linestring; else if (strcmp(typestring, MULTILINESTRING_TYPE) == 0) return Geometry::wkb_multilinestring; else if (strcmp(typestring, POLYGON_TYPE) == 0) return Geometry::wkb_polygon; else if (strcmp(typestring, MULTIPOLYGON_TYPE) == 0) return Geometry::wkb_multipolygon; else if (strcmp(typestring, GEOMETRYCOLLECTION_TYPE) == 0) return Geometry::wkb_geometrycollection; else return Geometry::wkb_invalid_type; } /** Takes a GeoJSON CRS object as input and parses it into a SRID. If user has supplied a SRID, the parsing will be ignored. GeoJSON support two types of CRS objects; named and linked. Linked CRS will force us to download CRS parameters from the web, which we do not allow. Thus, we will only parse named CRS URNs in the"urn:ogc:def:crs:EPSG::" and "EPSG:" namespaces. In addition, "urn:ogc:def:crs:OGC:1.3:CRS84" will be recognized as SRID 4326. Note that CRS object with value JSON null is valid. @param crs_object A GeoJSON CRS object to parse. @param result The WKB string the result will be appended to. @return false if the parsing was successful, or true if it didn't understand the CRS object provided. */ bool Item_func_geomfromgeojson:: parse_crs_object(const Json_object *crs_object) { if (m_user_provided_srid) return false; /* Check if required CRS members "type" and "properties" exists, and that they are of correct type according to GeoJSON specification. */ const Json_dom *type_member= my_find_member_ncase(crs_object, TYPE_MEMBER); const Json_dom *properties_member= my_find_member_ncase(crs_object, PROPERTIES_MEMBER); if (!is_member_valid(type_member, TYPE_MEMBER, Json_dom::J_STRING, false, NULL) || !is_member_valid(properties_member, PROPERTIES_MEMBER, Json_dom::J_OBJECT, false, NULL)) { return true; } // Check that this CRS is a named CRS, and not a linked CRS. const Json_string *type_member_str= down_cast(type_member); if (native_strcasecmp(type_member_str->value().c_str(), NAMED_CRS) != 0) { // CRS object is not a named CRS. my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } /* Check that CRS properties member has the required member "name" of type "string". */ const Json_object *properties_member_obj= down_cast(properties_member); const Json_dom *crs_name_member= my_find_member_ncase(properties_member_obj, CRS_NAME_MEMBER); if (!is_member_valid(crs_name_member, CRS_NAME_MEMBER, Json_dom::J_STRING, false, NULL)) { return true; } /* Now we can do the parsing of named CRS. The parsing happens as follows: 1) Check if the named CRS is equal to urn:ogc:def:crs:OGC:1.3:CRS84". If so, return SRID 4326. 2) Otherwise, check if we have a short or long format CRS URN in the EPSG namespace. 3) If we have a CRS URN in the EPSG namespace, check if the ending after the last ':' is a valid SRID ("EPSG:" or "urn:ogc:def:crs:EPSG::"). An valid SRID must be greater than zero, and less than or equal to UINT_MAX32. 4) If a SRID was returned from the parsing, check if we already have found a valid CRS earlier in the parsing. If so, and the SRID from the earlier CRS was different than the current, return an error to the user. If any of these fail, an error is returned to the user. */ const Json_string *crs_name_member_str= down_cast(crs_name_member); longlong parsed_srid= -1; if (native_strcasecmp(crs_name_member_str->value().c_str(), CRS84_URN) == 0) { parsed_srid= 4326; } else { size_t start_index; size_t name_length= crs_name_member_str->size(); const char *crs_name= crs_name_member_str->value().c_str(); if (native_strncasecmp(crs_name, SHORT_EPSG_PREFIX, 5) == 0) { start_index= 5; } else if (native_strncasecmp(crs_name, LONG_EPSG_PREFIX, 22) == 0) { start_index= 22; } else { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } char *end_of_parse; longlong parsed_value= strtoll(crs_name + start_index, &end_of_parse, 10); /* Check that the whole ending got parsed, and that the value is within valid SRID range. */ if (end_of_parse == (crs_name + name_length) && parsed_value > 0 && parsed_value <= UINT_MAX32) { parsed_srid= static_cast(parsed_value); } else { my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } } if (parsed_srid > 0) { if (m_srid_found_in_document > 0 && parsed_srid != m_srid_found_in_document) { // A SRID has already been found, which had a different value. my_error(ER_INVALID_GEOJSON_UNSPECIFIED, MYF(0), func_name()); return true; } else { m_srid_found_in_document= parsed_srid; } } return false; } /** Checks if a JSON member is valid based on input criteria. This function checks if the provided member exists, and if it's of the expected type. If it fails ome of the test, my_error() is called and false is returned from the function. @param member The member to validate. @param member_name Name of the member we are validating, so that the error returned to the user is more informative. @param expected_type Expected type of the member. @param allow_null If we shold allow the member to have JSON null value. @param[out] was_null This will be set to true if the provided member had a JSON null value. Is only affected if allow_null is set to true. @return true if the member is valid, false otherwise. */ bool Item_func_geomfromgeojson:: is_member_valid(const Json_dom *member, const char *member_name, Json_dom::enum_json_type expected_type, bool allow_null, bool *was_null) { if (member == NULL) { my_error(ER_INVALID_GEOJSON_MISSING_MEMBER, MYF(0), func_name(), member_name); return false; } if (allow_null) { assert(was_null != NULL); *was_null= member->json_type() == Json_dom::J_NULL; if (*was_null) return true; } const char *type_name; if (member->json_type() != expected_type) { switch (expected_type) { case Json_dom::J_OBJECT: type_name= "object"; break; case Json_dom::J_ARRAY: type_name= "array"; break; case Json_dom::J_STRING: type_name= "string"; break; default: /* purecov: begin deadcode */ assert(false); return false; /* purecov: end */ } my_error(ER_INVALID_GEOJSON_WRONG_TYPE, MYF(0), func_name(), member_name, type_name); return false; } return true; } void Item_func_geomfromgeojson::fix_length_and_dec() { Item_geometry_func::fix_length_and_dec(); } /** Checks if the supplied argument is a valid integer type. The function will fail if the supplied data is binary data. It will accept strings as integer type. Used for checking SRID and OPTIONS argument. @param argument The argument to check. @return true if the argument is a valid integer type, false otherwise. */ bool Item_func_geomfromgeojson::check_argument_valid_integer(Item *argument) { bool is_binary_charset= (argument->collation.collation == &my_charset_bin); bool is_parameter_marker= (argument->type() == PARAM_ITEM); switch (argument->field_type()) { case MYSQL_TYPE_NULL: return true; case MYSQL_TYPE_STRING: case MYSQL_TYPE_VARCHAR: case MYSQL_TYPE_VAR_STRING: return (!is_binary_charset || is_parameter_marker); case MYSQL_TYPE_INT24: case MYSQL_TYPE_LONG: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_TINY: return true; default: return false; } } /** Do type checking on all provided arguments, as well as settings maybe_null to the appropriate value. */ bool Item_func_geomfromgeojson::fix_fields(THD *thd, Item **ref) { if (Item_geometry_func::fix_fields(thd, ref)) return true; switch (arg_count) { case 3: { // Validate SRID argument if (!check_argument_valid_integer(args[2])) { my_error(ER_INCORRECT_TYPE, MYF(0), "SRID", func_name()); return true; } maybe_null= (args[0]->maybe_null || args[1]->maybe_null || args[2]->maybe_null); } // Fall through. case 2: { // Validate options argument if (!check_argument_valid_integer(args[1])) { my_error(ER_INCORRECT_TYPE, MYF(0), "options", func_name()); return true; } maybe_null= (args[0]->maybe_null || args[1]->maybe_null); } // Fall through. case 1: { /* Validate GeoJSON argument type. We do not allow binary data as GeoJSON argument. */ bool is_binary_charset= (args[0]->collation.collation == &my_charset_bin); bool is_parameter_marker= (args[0]->type() == PARAM_ITEM); switch (args[0]->field_type()) { case MYSQL_TYPE_NULL: break; case MYSQL_TYPE_JSON: case MYSQL_TYPE_VARCHAR: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: if (is_binary_charset && !is_parameter_marker) { my_error(ER_INCORRECT_TYPE, MYF(0), "geojson", func_name()); return true; } break; default: my_error(ER_INCORRECT_TYPE, MYF(0), "geojson", func_name()); return true; } maybe_null= args[0]->maybe_null; break; } } /* Set maybe_null always to true. This is because the following GeoJSON input will return SQL NULL: { "type": "Feature", "geometry": null, "properties": { "name": "Foo Bar" } } */ maybe_null= true; return false; } /** Converts a wkbType to the corresponding GeoJSON type. @param type The WKB Type to convert. @return The corresponding GeoJSON type, or NULL if no such type exists. */ const char * wkbtype_to_geojson_type(Geometry::wkbType type) { switch (type) { case Geometry::wkb_geometrycollection: return Item_func_geomfromgeojson::GEOMETRYCOLLECTION_TYPE; case Geometry::wkb_point: return Item_func_geomfromgeojson::POINT_TYPE; case Geometry::wkb_multipoint: return Item_func_geomfromgeojson::MULTIPOINT_TYPE; case Geometry::wkb_linestring: return Item_func_geomfromgeojson::LINESTRING_TYPE; case Geometry::wkb_multilinestring: return Item_func_geomfromgeojson::MULTILINESTRING_TYPE; case Geometry::wkb_polygon: return Item_func_geomfromgeojson::POLYGON_TYPE; case Geometry::wkb_multipolygon: return Item_func_geomfromgeojson::MULTIPOLYGON_TYPE; case Geometry::wkb_invalid_type: case Geometry::wkb_polygon_inner_rings: default: return NULL; } } /** Append a GeoJSON array with coordinates to the writer at the current position. The WKB parser must be positioned at the beginning of the coordinates. There must exactly two coordinates in the array (x and y). The coordinates are rounded to the number of decimals specified in the variable max_decimal_digits.: max_decimal_digits == 2: 12.789 => 12.79 10 => 10.00 @param parser WKB parser with position set to the beginning of the coordinates. @param array JSON array to append the result to. @param mbr A bounding box, which will be updated with data from the coordinates. @param calling_function Name of user-invoked function @param max_decimal_digits Max length of decimal numbers @param add_bounding_box True if a bounding box should be added @param add_short_crs_urn True if we should add short format CRS URN @param add_long_crs_urn True if we should add long format CRS URN @param geometry_srid Spacial Reference System Identifier being used @return false on success, true otherwise. */ bool append_coordinates(Geometry::wkb_parser *parser, Json_array *array, MBR *mbr, const char *calling_function, int max_decimal_digits, bool add_bounding_box, bool add_short_crs_urn, bool add_long_crs_urn, uint32 geometry_srid) { point_xy coordinates; if (parser->scan_xy(&coordinates)) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } double x_value= my_double_round(coordinates.x, max_decimal_digits, true, false); double y_value= my_double_round(coordinates.y, max_decimal_digits, true, false); if (array->append_alias(new (std::nothrow) Json_double(x_value)) || array->append_alias(new (std::nothrow) Json_double(y_value))) { return true; } if (add_bounding_box) mbr->add_xy(x_value, y_value); return false; } /** Append a GeoJSON LineString object to the writer at the current position. The parser must be positioned after the LineString header, and there must be at least one coordinate array in the linestring. @param parser WKB parser with position set to after the LineString header. @param points JSON array to append the result to. @param mbr A bounding box, which will be updated with data from the LineString. @param calling_function Name of user-invoked function @param max_decimal_digits Max length of decimal numbers @param add_bounding_box True if a bounding box should be added @param add_short_crs_urn True if we should add short format CRS URN @param add_long_crs_urn True if we should add long format CRS URN @param geometry_srid Spacial Reference System Identifier being used @return false on success, true otherwise. */ bool append_linestring(Geometry::wkb_parser *parser, Json_array *points, MBR *mbr, const char *calling_function, int max_decimal_digits, bool add_bounding_box, bool add_short_crs_urn, bool add_long_crs_urn, uint32 geometry_srid) { uint32 num_points= 0; if (parser->scan_non_zero_uint4(&num_points)) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } while (num_points--) { Json_array *point= new (std::nothrow) Json_array(); if (point == NULL || points->append_alias(point) || append_coordinates(parser, point, mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid)) { return true; } } return false; } /** Append a GeoJSON Polygon object to the writer at the current position. The parser must be positioned after the Polygon header, and all coordinate arrays must contain at least one value. @param parser WKB parser with position set to after the Polygon header. @param polygon_rings JSON array to append the result to. @param mbr A bounding box, which will be updated with data from the Polygon. @param calling_function Name of user-invoked function @param max_decimal_digits Max length of decimal numbers @param add_bounding_box True if a bounding box should be added @param add_short_crs_urn True if we should add short format CRS URN @param add_long_crs_urn True if we should add long format CRS URN @param geometry_srid Spacial Reference System Identifier being used @return false on success, true otherwise. */ bool append_polygon(Geometry::wkb_parser *parser, Json_array *polygon_rings, MBR *mbr, const char *calling_function, int max_decimal_digits, bool add_bounding_box, bool add_short_crs_urn, bool add_long_crs_urn, uint32 geometry_srid) { uint32 num_inner_rings= 0; if (parser->scan_non_zero_uint4(&num_inner_rings)) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } while (num_inner_rings--) { Json_array *polygon_ring= new (std::nothrow) Json_array(); if (polygon_ring == NULL || polygon_rings->append_alias(polygon_ring)) return true; uint32 num_points= 0; if (parser->scan_non_zero_uint4(&num_points)) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } while (num_points--) { Json_array *point = new (std::nothrow) Json_array(); if (point == NULL || polygon_ring->append_alias(point) || append_coordinates(parser, point, mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid)) { return true; } } } return false; } /** Appends a GeoJSON bounding box to the rapidjson output buffer. @param mbr Bounding box to write. @param geometry The JSON object to append the bounding box to. @return false on success, true otherwise. */ bool append_bounding_box(MBR *mbr, Json_object *geometry) { assert(GEOM_DIM == 2); Json_array *bbox_array= new (std::nothrow) Json_array(); if (bbox_array == NULL || geometry->add_alias("bbox", bbox_array) || bbox_array->append_alias(new (std::nothrow) Json_double(mbr->xmin)) || bbox_array->append_alias(new (std::nothrow) Json_double(mbr->ymin)) || bbox_array->append_alias(new (std::nothrow) Json_double(mbr->xmax)) || bbox_array->append_alias(new (std::nothrow) Json_double(mbr->ymax))) { return true; } return false; } /** Appends a GeoJSON CRS object to the rapidjson output buffer. If both add_long_crs_urn and add_short_crs_urn is specified, the long CRS URN is preferred as mentioned in the GeoJSON specification: "OGC CRS URNs such as "urn:ogc:def:crs:OGC:1.3:CRS84" shall be preferred over legacy identifiers such as "EPSG:4326"" @param geometry The JSON object to append the CRS object to. @param max_decimal_digits Max length of decimal numbers @param add_bounding_box True if a bounding box should be added @param add_short_crs_urn True if we should add short format CRS URN @param add_long_crs_urn True if we should add long format CRS URN @param geometry_srid Spacial Reference System Identifier being used @return false on success, true otherwise. */ bool append_crs(Json_object *geometry, int max_decimal_digits, bool add_bounding_box, bool add_short_crs_urn, bool add_long_crs_urn, uint32 geometry_srid) { assert(add_long_crs_urn || add_short_crs_urn); assert(geometry_srid > 0); Json_object *crs_object= new (std::nothrow) Json_object(); if (crs_object == NULL || geometry->add_alias("crs", crs_object) || crs_object->add_alias("type", new (std::nothrow) Json_string("name"))) { return true; } Json_object *crs_properties= new (std::nothrow) Json_object(); if (crs_properties == NULL || crs_object->add_alias("properties", crs_properties)) { return true; } // Max width of SRID + '\0' char srid_string[MAX_INT_WIDTH + 1]; llstr(geometry_srid, srid_string); char crs_name[MAX_CRS_WIDTH]; if (add_long_crs_urn) strcpy(crs_name, Item_func_geomfromgeojson::LONG_EPSG_PREFIX); else if (add_short_crs_urn) strcpy(crs_name, Item_func_geomfromgeojson::SHORT_EPSG_PREFIX); strcat(crs_name, srid_string); if (crs_properties->add_alias("name", new (std::nothrow) Json_string(crs_name))) { return true; } return false; } /** Reads a WKB GEOMETRY from input and writes the equivalent GeoJSON to the output. If a GEOMETRYCOLLECTION is found, this function will call itself for each GEOMETRY in the collection. @param parser The WKB input to read from, positioned at the start of GEOMETRY header. @param geometry JSON object to append the result to. @param is_root_object Indicating if the current GEOMETRY is the root object in the output GeoJSON. @param mbr A bounding box, which will be updated with data from all the GEOMETRIES found in the input. @param calling_function Name of the user-invoked function @param max_decimal_digits Max length of decimal numbers @param add_bounding_box True if a bounding box should be added @param add_short_crs_urn True if we should add short format CRS URN @param add_long_crs_urn True if we should add long format CRS URN @param geometry_srid Spacial Reference System Identifier being used @return false on success, true otherwise. */ bool append_geometry(Geometry::wkb_parser *parser, Json_object *geometry, bool is_root_object, MBR *mbr, const char *calling_function, int max_decimal_digits, bool add_bounding_box, bool add_short_crs_urn, bool add_long_crs_urn, uint32 geometry_srid) { // Check of wkb_type is within allowed range. wkb_header header; if (parser->scan_wkb_header(&header) || header.wkb_type < Geometry::wkb_first || header.wkb_type > Geometry::wkb_geometrycollection) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } const char *geojson_type= wkbtype_to_geojson_type(static_cast(header.wkb_type)); if (geometry->add_alias("type", new (std::nothrow) Json_string(geojson_type))) return true; /* Use is_mbr_empty to check if we encounter any empty GEOMETRY collections. In that case, we don't want to write a bounding box to the GeoJSON output. */ bool is_mbr_empty= false; switch (header.wkb_type) { case Geometry::wkb_point: { Json_array *point= new (std::nothrow) Json_array(); if (point == NULL || geometry->add_alias("coordinates", point) || append_coordinates(parser, point, mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid)) { return true; } break; } case Geometry::wkb_linestring: { Json_array *points= new (std::nothrow) Json_array(); if (points == NULL || geometry->add_alias("coordinates", points) || append_linestring(parser, points, mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid)) { return true; } break; } case Geometry::wkb_polygon: { Json_array *polygon_rings= new (std::nothrow) Json_array(); if (polygon_rings == NULL || geometry->add_alias("coordinates", polygon_rings) || append_polygon(parser, polygon_rings, mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid)) { return true; } break; } case Geometry::wkb_multipoint: case Geometry::wkb_multipolygon: case Geometry::wkb_multilinestring: { uint32 num_items= 0; if (parser->scan_non_zero_uint4(&num_items)) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } Json_array *collection= new (std::nothrow) Json_array(); if (collection == NULL || geometry->add_alias("coordinates", collection)) return true; while (num_items--) { if (parser->skip_wkb_header()) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } else { bool result= false; Json_array *points= new (std::nothrow) Json_array(); if (points == NULL || collection->append_alias(points)) return true; if (header.wkb_type == Geometry::wkb_multipoint) result= append_coordinates(parser, points, mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid); else if (header.wkb_type == Geometry::wkb_multipolygon) result= append_polygon(parser, points, mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid); else if (header.wkb_type == Geometry::wkb_multilinestring) result= append_linestring(parser, points, mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid); else assert(false); if (result) return true; } } break; } case Geometry::wkb_geometrycollection: { uint32 num_geometries= 0; if (parser->scan_uint4(&num_geometries)) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } is_mbr_empty= (num_geometries == 0); Json_array *collection= new (std::nothrow) Json_array(); if (collection == NULL || geometry->add_alias("geometries", collection)) return true; while (num_geometries--) { // Create a new MBR for the collection. MBR subcollection_mbr; Json_object *sub_geometry= new (std::nothrow) Json_object(); if (sub_geometry == NULL || collection->append_alias(sub_geometry) || append_geometry(parser, sub_geometry, false, &subcollection_mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid)) { return true; } if (add_bounding_box) mbr->add_mbr(&subcollection_mbr); } break; } default: { // This should not happen, since we did a check on wkb_type earlier. /* purecov: begin inspected */ assert(false); return true; /* purecov: end inspected */ } } // Only add a CRS object if the SRID of the GEOMETRY is not 0. if (is_root_object && (add_long_crs_urn || add_short_crs_urn) && geometry_srid > 0) { append_crs(geometry, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, geometry_srid); } if (add_bounding_box && !is_mbr_empty) append_bounding_box(mbr, geometry); return false; } /** The contract for this function is found in item_json_func.h */ bool geometry_to_json(Json_wrapper *wr, Item *geometry_arg, const char *calling_function, int max_decimal_digits, bool add_bounding_box, bool add_short_crs_urn, bool add_long_crs_urn, uint32 *geometry_srid) { String arg_val; String *swkb= geometry_arg->val_str(&arg_val); if ((geometry_arg->null_value)) return false; Geometry::wkb_parser parser(swkb->ptr(), swkb->ptr() + swkb->length()); if (parser.scan_uint4(geometry_srid)) { my_error(ER_GIS_INVALID_DATA, MYF(0), calling_function); return true; } /* append_geometry() will go through the WKB and call itself recursivly if geometry collections are encountered. For each recursive call, a new MBR is created. The function will fail if it encounters invalid data in the WKB input. */ MBR mbr; Json_object *geojson_object= new (std::nothrow) Json_object(); if (geojson_object == NULL || append_geometry(&parser, geojson_object, true, &mbr, calling_function, max_decimal_digits, add_bounding_box, add_short_crs_urn, add_long_crs_urn, *geometry_srid)) { delete geojson_object; return true; } Json_wrapper w(geojson_object); wr->steal(&w); return false; } /** Create a GeoJSON object, according to GeoJSON specification revison 1.0. */ bool Item_func_as_geojson::val_json(Json_wrapper *wr) { assert(fixed == TRUE); if ((arg_count > 1 && parse_maxdecimaldigits_argument()) || (arg_count > 2 && parse_options_argument())) { if (null_value && !current_thd->is_error()) return false; else return error_json(); } /* Set maximum number of decimal digits. If maxdecimaldigits argument was not specified, set unlimited number of decimal digits. */ if (arg_count < 2) m_max_decimal_digits= INT_MAX32; if (geometry_to_json(wr, args[0], func_name(), m_max_decimal_digits, m_add_bounding_box, m_add_short_crs_urn, m_add_long_crs_urn, &m_geometry_srid)) { if (null_value && !current_thd->is_error()) return false; else return error_json(); } null_value= args[0]->null_value; return false; } /** Parse the value in options argument. Options is a 3-bit bitmask with the following options: 0 No options (default values). 1 Add a bounding box to the output. 2 Add a short CRS URN to the output. The default format is a short format ("EPSG:"). 4 Add a long format CRS URN ("urn:ogc:def:crs:EPSG::"). This will override option 2. E.g., bitmask 5 and 7 mean the same: add a bounding box and a long format CRS URN. If value is out of range (below zero or greater than seven), an error will be raised. This function expects that the options argument is the third argument in the function call. @return false on success, true otherwise (value out of range or similar). */ bool Item_func_as_geojson::parse_options_argument() { assert(arg_count > 2); longlong options_argument= args[2]->val_int(); if ((null_value= args[2]->null_value)) return true; if (options_argument < 0 || options_argument > 7) { char options_string[MAX_BIGINT_WIDTH + 1]; if (args[2]->unsigned_flag) ullstr(options_argument, options_string); else llstr(options_argument, options_string); my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "options", options_string, func_name()); return true; } m_add_bounding_box= options_argument & (1 << 0); m_add_short_crs_urn= options_argument & (1 << 1); m_add_long_crs_urn= options_argument & (1 << 2); if (m_add_long_crs_urn) m_add_short_crs_urn= false; return false; } /** Parse the value in maxdecimaldigits argument. This value MUST be a positive integer. If value is out of range (negative value or greater than INT_MAX), an error will be raised. This function expects that the maxdecimaldigits argument is the second argument in the function call. @return false on success, true otherwise (negative value or similar). */ bool Item_func_as_geojson::parse_maxdecimaldigits_argument() { assert(arg_count > 1); longlong max_decimal_digits_argument = args[1]->val_int(); if ((null_value= args[1]->null_value)) return true; if (max_decimal_digits_argument < 0 || max_decimal_digits_argument > INT_MAX32) { char max_decimal_digits_string[MAX_BIGINT_WIDTH + 1]; if (args[1]->unsigned_flag) ullstr(max_decimal_digits_argument, max_decimal_digits_string); else llstr(max_decimal_digits_argument, max_decimal_digits_string); my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "max decimal digits", max_decimal_digits_string, func_name()); return true; } m_max_decimal_digits= static_cast(max_decimal_digits_argument); return false; } /** Perform type checking on all arguments: argument must be a geometry. must be an integer value. must be an integer value. Set maybe_null to the correct value. */ bool Item_func_as_geojson::fix_fields(THD *thd, Item **ref) { if (Item_json_func::fix_fields(thd, ref)) return true; /* We must set maybe_null to true, since the GeoJSON string may be longer than the packet size. */ maybe_null= true; // Check if geometry argument is a geometry type. bool is_parameter_marker= (args[0]->type() == PARAM_ITEM); switch (args[0]->field_type()) { case MYSQL_TYPE_GEOMETRY: case MYSQL_TYPE_NULL: break; default: { if (!is_parameter_marker) { my_error(ER_INCORRECT_TYPE, MYF(0), "geojson", func_name()); return true; } } } if (arg_count > 1) { if (!Item_func_geomfromgeojson::check_argument_valid_integer(args[1])) { my_error(ER_INCORRECT_TYPE, MYF(0), "max decimal digits", func_name()); return true; } } if (arg_count > 2) { if (!Item_func_geomfromgeojson::check_argument_valid_integer(args[2])) { my_error(ER_INCORRECT_TYPE, MYF(0), "options", func_name()); return true; } } return false; } String *Item_func_as_wkt::val_str_ascii(String *str) { assert(fixed == 1); String arg_val; String *swkb= args[0]->val_str(&arg_val); Geometry_buffer buffer; Geometry *geom= NULL; if ((null_value= (!swkb || args[0]->null_value))) return 0; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } str->length(0); if ((null_value= geom->as_wkt(str))) return 0; return str; } void Item_func_as_wkt::fix_length_and_dec() { collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII); max_length=MAX_BLOB_WIDTH; maybe_null= 1; } String *Item_func_as_wkb::val_str(String *str) { assert(fixed == 1); String arg_val; String *swkb= args[0]->val_str(&arg_val); Geometry_buffer buffer; if ((null_value= (!swkb || args[0]->null_value))) return 0; if (!(Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } str->copy(swkb->ptr() + SRID_SIZE, swkb->length() - SRID_SIZE, &my_charset_bin); return str; } String *Item_func_geometry_type::val_str_ascii(String *str) { assert(fixed == 1); String *swkb= args[0]->val_str(str); Geometry_buffer buffer; Geometry *geom= NULL; if ((null_value= (!swkb || args[0]->null_value))) return 0; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } /* String will not move */ str->copy(geom->get_class_info()->m_name.str, geom->get_class_info()->m_name.length, &my_charset_latin1); return str; } String *Item_func_validate::val_str(String *str) { assert(fixed == 1); String *swkb= args[0]->val_str(&arg_val); Geometry_buffer buffer; Geometry *geom= NULL; if ((null_value= (!swkb || args[0]->null_value))) return error_str(); if (!(geom= Geometry::construct(&buffer, swkb))) return error_str(); if (geom->get_srid() != 0) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); return error_str(); } int isvalid= 0; try { isvalid= check_geometry_valid(geom); } catch (...) { null_value= true; handle_gis_exception("ST_Validate"); } return isvalid ? swkb : error_str(); } Field::geometry_type Item_func_make_envelope::get_geometry_type() const { return Field::GEOM_POLYGON; } String *Item_func_make_envelope::val_str(String *str) { assert(fixed == 1); String arg_val1, arg_val2; String *pt1= args[0]->val_str(&arg_val1); String *pt2= args[1]->val_str(&arg_val2); Geometry_buffer buffer1, buffer2; Geometry *geom1= NULL, *geom2= NULL; uint32 srid; if ((null_value= (!pt1 || !pt2 || args[0]->null_value || args[1]->null_value))) return error_str(); if ((null_value= (!(geom1= Geometry::construct(&buffer1, pt1)) || !(geom2= Geometry::construct(&buffer2, pt2))))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } if (geom1->get_type() != Geometry::wkb_point || geom2->get_type() != Geometry::wkb_point || geom1->get_srid() != 0 || geom2->get_srid() != 0) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); return error_str(); } if (geom1->get_srid() != geom2->get_srid()) { my_error(ER_GIS_DIFFERENT_SRIDS, MYF(0), func_name(), geom1->get_srid(), geom2->get_srid()); return error_str(); } Gis_point *gpt1= static_cast(geom1); Gis_point *gpt2= static_cast(geom2); double x1= gpt1->get<0>(), y1= gpt1->get<1>(); double x2= gpt2->get<0>(), y2= gpt2->get<1>(); if (!my_isfinite(x1) || !my_isfinite(x2) || !my_isfinite(y1) || !my_isfinite(y2)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } MBR mbr; if (x1 < x2) { mbr.xmin= x1; mbr.xmax= x2; } else { mbr.xmin= x2; mbr.xmax= x1; } if (y1 < y2) { mbr.ymin= y1; mbr.ymax= y2; } else { mbr.ymin= y2; mbr.ymax= y1; } int dim= mbr.dimension(); assert(dim >= 0); /* Use default invalid SRID because we are computing the envelope on an "abstract plain", that is, we are not aware of any detailed information of the coordinate system, we simply suppose the points given to us are in the abstract cartesian coordinate system, so we always use the point with minimum coordinates of all dimensions and the point with maximum coordinates of all dimensions and combine the two groups of coordinate values to get 2^N points for the N dimension points. */ srid= 0; str->set_charset(&my_charset_bin); str->length(0); if (str->reserve(GEOM_HEADER_SIZE + 4 + 4 + 5 * POINT_DATA_SIZE, 128)) return error_str(); str->q_append(srid); str->q_append(static_cast(Geometry::wkb_ndr)); if (dim == 0) { str->q_append(static_cast(Geometry::wkb_point)); str->q_append(mbr.xmin); str->q_append(mbr.ymin); } else if (dim == 1) { str->q_append(static_cast(Geometry::wkb_linestring)); str->q_append(static_cast(2)); str->q_append(mbr.xmin); str->q_append(mbr.ymin); str->q_append(mbr.xmax); str->q_append(mbr.ymax); } else { assert(dim == 2); str->q_append(static_cast(Geometry::wkb_polygon)); str->q_append(static_cast(1)); str->q_append(static_cast(5)); str->q_append(mbr.xmin); str->q_append(mbr.ymin); str->q_append(mbr.xmax); str->q_append(mbr.ymin); str->q_append(mbr.xmax); str->q_append(mbr.ymax); str->q_append(mbr.xmin); str->q_append(mbr.ymax); str->q_append(mbr.xmin); str->q_append(mbr.ymin); } return str; } Field::geometry_type Item_func_envelope::get_geometry_type() const { return Field::GEOM_GEOMETRY; } String *Item_func_envelope::val_str(String *str) { assert(fixed == 1); String arg_val; String *swkb= args[0]->val_str(&arg_val); Geometry_buffer buffer; Geometry *geom= NULL; uint32 srid; if ((null_value= (!swkb || args[0]->null_value))) { assert(!swkb && args[0]->null_value); return NULL; } if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } srid= uint4korr(swkb->ptr()); str->set_charset(&my_charset_bin); str->length(0); if (str->reserve(SRID_SIZE, 512)) return error_str(); str->q_append(srid); if ((null_value= geom->envelope(str))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } return str; } Field::geometry_type Item_func_centroid::get_geometry_type() const { return Field::GEOM_POINT; } String *Item_func_centroid::val_str(String *str) { assert(fixed == 1); String arg_val; String *swkb= args[0]->val_str(&arg_val); Geometry_buffer buffer; Geometry *geom= NULL; if ((null_value= (!swkb || args[0]->null_value))) return NULL; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } str->length(0); str->set_charset(&my_charset_bin); if (geom->get_geotype() != Geometry::wkb_geometrycollection && geom->normalize_ring_order() == NULL) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } null_value= bg_centroid(geom, str); if (null_value) return error_str(); return str; } /** Accumulate a geometry's all vertex points into a multipoint. It implements the WKB_scanner_event_handler interface so as to be registered into wkb_scanner and be notified of WKB data events. */ class Point_accumulator : public WKB_scanner_event_handler { Gis_multi_point *m_mpts; const void *pt_start; public: explicit Point_accumulator(Gis_multi_point *mpts) :m_mpts(mpts), pt_start(NULL) { } virtual void on_wkb_start(Geometry::wkbByteOrder bo, Geometry::wkbType geotype, const void *wkb, uint32 len, bool has_hdr) { if (geotype == Geometry::wkb_point) { Gis_point pt(wkb, POINT_DATA_SIZE, Geometry::Flags_t(Geometry::wkb_point, len), m_mpts->get_srid()); m_mpts->push_back(pt); pt_start= wkb; } } virtual void on_wkb_end(const void *wkb) { if (pt_start) assert(static_cast(pt_start) + POINT_DATA_SIZE == wkb); pt_start= NULL; } }; /** Retrieve from a geometry collection geometries of the same base type into a multi-xxx geometry object. For example, group all points and multipoints into a single multipoint object, where the base type is point. @tparam Base_type the base type to group. */ template class Geometry_grouper : public WKB_scanner_event_handler { std::vector m_types; std::vector m_ptrs; typedef Gis_wkb_vector Group_type; Group_type *m_group; Gis_geometry_collection *m_collection; String *m_gcbuf; Geometry::wkbType m_target_type; public: explicit Geometry_grouper(Group_type *out) :m_group(out), m_collection(NULL), m_gcbuf(NULL) { switch (out->get_type()) { case Geometry::wkb_multipoint: m_target_type= Geometry::wkb_point; break; case Geometry::wkb_multilinestring: m_target_type= Geometry::wkb_linestring; break; case Geometry::wkb_multipolygon: m_target_type= Geometry::wkb_polygon; break; default: assert(false); break; } } /* Group polygons and multipolygons into a geometry collection. */ Geometry_grouper(Gis_geometry_collection *out, String *gcbuf) :m_group(NULL), m_collection(out), m_gcbuf(gcbuf) { m_target_type= Geometry::wkb_polygon; assert(out != NULL && gcbuf != NULL); } virtual void on_wkb_start(Geometry::wkbByteOrder bo, Geometry::wkbType geotype, const void *wkb, uint32 len, bool has_hdr) { m_types.push_back(geotype); m_ptrs.push_back(wkb); if (m_types.size() == 1) assert(geotype == Geometry::wkb_geometrycollection); } virtual void on_wkb_end(const void *wkb_end) { Geometry::wkbType geotype= m_types.back(); m_types.pop_back(); const void *wkb_start= m_ptrs.back(); m_ptrs.pop_back(); if (geotype != m_target_type || m_types.size() == 0) return; Geometry::wkbType ptype= m_types.back(); size_t len= static_cast(wkb_end) - static_cast(wkb_start); /* We only group independent geometries, points in linestrings or polygons are not independent, nor are linestrings in polygons. */ if (m_target_type == geotype && m_group != NULL && ((m_target_type == Geometry::wkb_point && (ptype == Geometry::wkb_geometrycollection || ptype == Geometry::wkb_multipoint)) || (m_target_type == Geometry::wkb_linestring && (ptype == Geometry::wkb_geometrycollection || ptype == Geometry::wkb_multilinestring)) || (m_target_type == Geometry::wkb_polygon && (ptype == Geometry::wkb_geometrycollection || ptype == Geometry::wkb_multipolygon)))) { Base_type g(wkb_start, len, Geometry::Flags_t(m_target_type, 0), 0); m_group->push_back(g); assert(m_collection == NULL && m_gcbuf == NULL); } if (m_collection != NULL && (geotype == Geometry::wkb_polygon || geotype == Geometry::wkb_multipolygon)) { assert(m_group == NULL && m_gcbuf != NULL); String str(static_cast(wkb_start), len, &my_charset_bin); m_collection->append_geometry(m_collection->get_srid(), geotype, &str, m_gcbuf); } } }; /* Compute a geometry collection's centroid in demension decreasing order: If it has polygons, make them a multipolygon and compute its centroid as the result; otherwise compose a multilinestring and compute its centroid as the result; otherwise compose a multipoint and compute its centroid as the result. @param geom the geometry collection. @param[out] respt takes out the centroid point result. @param[out] null_value returns whether the result is NULL. @return whether got error, true if got error and false if successful. */ template bool geometry_collection_centroid(const Geometry *geom, typename BG_models:: Point *respt, my_bool *null_value) { typename BG_models::Multipolygon mplgn; Geometry_grouper::Polygon> plgn_grouper(&mplgn); const char *wkb_start= geom->get_cptr(); uint32 wkb_len0, wkb_len= geom->get_data_size(); *null_value= false; /* The geometries with largest dimension determine the centroid, because components of lower dimensions weighs nothing in comparison. */ wkb_len0= wkb_len; wkb_scanner(wkb_start, &wkb_len, Geometry::wkb_geometrycollection, false, &plgn_grouper); if (mplgn.size() > 0) { if (mplgn.normalize_ring_order() == NULL) return true; boost::geometry::centroid(mplgn, *respt); } else { typename BG_models::Multilinestring mls; wkb_len= wkb_len0; Geometry_grouper::Linestring> ls_grouper(&mls); wkb_scanner(wkb_start, &wkb_len, Geometry::wkb_geometrycollection, false, &ls_grouper); if (mls.size() > 0) boost::geometry::centroid(mls, *respt); else { typename BG_models::Multipoint mpts; wkb_len= wkb_len0; Geometry_grouper::Point> pt_grouper(&mpts); wkb_scanner(wkb_start, &wkb_len, Geometry::wkb_geometrycollection, false, &pt_grouper); if (mpts.size() > 0) boost::geometry::centroid(mpts, *respt); else *null_value= true; } } return false; } template bool Item_func_centroid::bg_centroid(const Geometry *geom, String *ptwkb) { typename BG_models::Point respt; // Release last call's result buffer. bg_resbuf_mgr.free_result_buffer(); try { switch (geom->get_type()) { case Geometry::wkb_point: { typename BG_models::Point geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::centroid(geo, respt); } break; case Geometry::wkb_multipoint: { typename BG_models::Multipoint geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::centroid(geo, respt); } break; case Geometry::wkb_linestring: { typename BG_models::Linestring geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::centroid(geo, respt); } break; case Geometry::wkb_multilinestring: { typename BG_models::Multilinestring geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::centroid(geo, respt); } break; case Geometry::wkb_polygon: { typename BG_models::Polygon geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::centroid(geo, respt); } break; case Geometry::wkb_multipolygon: { typename BG_models::Multipolygon geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::centroid(geo, respt); } break; case Geometry::wkb_geometrycollection: if (geometry_collection_centroid(geom, &respt, &null_value)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); null_value= true; } break; default: assert(false); break; } respt.set_srid(geom->get_srid()); if (!null_value) null_value= post_fix_result(&bg_resbuf_mgr, respt, ptwkb); if (!null_value) bg_resbuf_mgr.set_result_buffer(const_cast(ptwkb->ptr())); } catch (...) { null_value= true; handle_gis_exception("st_centroid"); } return null_value; } Field::geometry_type Item_func_convex_hull::get_geometry_type() const { return Field::GEOM_GEOMETRY; } String *Item_func_convex_hull::val_str(String *str) { String arg_val; String *swkb= args[0]->val_str(&arg_val); Geometry_buffer buffer; Geometry *geom= NULL; if ((null_value= (!swkb || args[0]->null_value))) return NULL; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } assert(geom->get_coordsys() == Geometry::cartesian); str->set_charset(&my_charset_bin); str->length(0); if (geom->get_geotype() != Geometry::wkb_geometrycollection && geom->normalize_ring_order() == NULL) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } if (bg_convex_hull(geom, str)) return error_str(); // By taking over, str owns swkt->ptr and the memory will be released when // str points to another buffer in next call of this function // (done in post_fix_result), or when str's owner Item_xxx node is destroyed. if (geom->get_type() == Geometry::wkb_point) str->takeover(*swkb); return str; } template bool Item_func_convex_hull::bg_convex_hull(const Geometry *geom, String *res_hull) { typename BG_models::Polygon hull; typename BG_models::Linestring line_hull; Geometry::wkbType geotype= geom->get_type(); // Release last call's result buffer. bg_resbuf_mgr.free_result_buffer(); try { if (geotype == Geometry::wkb_multipoint || geotype == Geometry::wkb_linestring || geotype == Geometry::wkb_multilinestring || geotype == Geometry::wkb_geometrycollection) { /* It's likely that the multilinestring, linestring, geometry collection and multipoint have all colinear points so the final hull is a linear hull. If so we must get the linear hull otherwise we will get an invalid polygon hull. */ typename BG_models::Multipoint mpts; Point_accumulator pt_acc(&mpts); const char *wkb_start= geom->get_cptr(); uint32 wkb_len= geom->get_data_size(); wkb_scanner(wkb_start, &wkb_len, geotype, false, &pt_acc); bool isdone= true; if (mpts.size() == 0) return (null_value= true); // Make mpts unique as required by the is_colinear() algorithm. typename BG_models::Multipoint distinct_pts; distinct_pts.resize(mpts.size()); std::sort(mpts.begin(), mpts.end(), bgpt_lt()); typename BG_models::Multipoint::iterator itr= std::unique_copy(mpts.begin(), mpts.end(), distinct_pts.begin(), bgpt_eq()); distinct_pts.resize(itr - distinct_pts.begin()); if (is_colinear(distinct_pts)) { if (distinct_pts.size() == 1) { // Here we have to create a brand new point because res_hull will // take over its memory, which can't be done to distinct_pts[0]. typename BG_models::Point pt_hull= distinct_pts[0]; pt_hull.set_srid(geom->get_srid()); null_value= post_fix_result(&bg_resbuf_mgr, pt_hull, res_hull); } else { boost::geometry::convex_hull(distinct_pts, line_hull); line_hull.set_srid(geom->get_srid()); // The linestring consists of 4 or more points, but only the // first two contain real data, so we need to trim it down. line_hull.resize(2); null_value= post_fix_result(&bg_resbuf_mgr, line_hull, res_hull); } } else if (geotype == Geometry::wkb_geometrycollection) { boost::geometry::convex_hull(mpts, hull); hull.set_srid(geom->get_srid()); null_value= post_fix_result(&bg_resbuf_mgr, hull, res_hull); } else isdone= false; if (isdone) { if (!null_value) bg_resbuf_mgr.set_result_buffer(const_cast(res_hull->ptr())); return null_value; } } /* From here on we don't have to consider linear hulls, it's impossible. In theory we can use above multipoint to get convex hull for all 7 types of geometries, however we'd better use BG standard logic for each type, a tricky example would be: imagine an invalid polygon whose inner ring is completely contains its outer ring inside, BG might return the outer ring but if using the multipoint to get convexhull, we would get the inner ring as result instead. */ switch (geotype) { case Geometry::wkb_point: { /* A point's convex hull is the point itself, directly use the point's WKB buffer, set its header info correctly. */ assert(geom->get_ownmem() == false && geom->has_geom_header_space()); char *p= geom->get_cptr() - GEOM_HEADER_SIZE; write_geometry_header(p, geom->get_srid(), geom->get_geotype()); return false; } break; case Geometry::wkb_multipoint: { typename BG_models::Multipoint geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::convex_hull(geo, hull); } break; case Geometry::wkb_linestring: { typename BG_models::Linestring geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::convex_hull(geo, hull); } break; case Geometry::wkb_multilinestring: { typename BG_models::Multilinestring geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::convex_hull(geo, hull); } break; case Geometry::wkb_polygon: { typename BG_models::Polygon geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::convex_hull(geo, hull); } break; case Geometry::wkb_multipolygon: { typename BG_models::Multipolygon geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); boost::geometry::convex_hull(geo, hull); } break; case Geometry::wkb_geometrycollection: // Handled above. assert(false); break; default: break; } hull.set_srid(geom->get_srid()); null_value= post_fix_result(&bg_resbuf_mgr, hull, res_hull); if (!null_value) bg_resbuf_mgr.set_result_buffer(const_cast(res_hull->ptr())); } catch (...) { null_value= true; handle_gis_exception("st_convexhull"); } return null_value; } String *Item_func_simplify::val_str(String *str) { assert(fixed == 1); String *swkb= args[0]->val_str(&arg_val); double max_dist= args[1]->val_real(); Geometry_buffer buffer; Geometry *geom= NULL; // Release last call's result buffer. bg_resbuf_mgr.free_result_buffer(); if ((null_value= (!swkb || args[0]->null_value || args[1]->null_value))) return error_str(); if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } if (max_dist <= 0 || boost::math::isnan(max_dist)) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); return error_str(); } Geometry::wkbType gtype= geom->get_type(); try { if (gtype == Geometry::wkb_geometrycollection) { BG_geometry_collection bggc; bggc.fill(geom); Gis_geometry_collection gc(geom->get_srid(), Geometry::wkb_invalid_type, NULL, str); for (BG_geometry_collection::Geometry_list::iterator i= bggc.get_geometries().begin(); i != bggc.get_geometries().end(); ++i) { String gbuf; if ((null_value= simplify_basic (*i, max_dist, &gbuf, &gc, str))) return error_str(); } } else { if ((null_value= simplify_basic(geom, max_dist, str))) return error_str(); else bg_resbuf_mgr.set_result_buffer(const_cast(str->ptr())); } } catch (...) { null_value= true; handle_gis_exception("ST_Simplify"); } return str; } template int Item_func_simplify:: simplify_basic(Geometry *geom, double max_dist, String *str, Gis_geometry_collection *gc, String *gcbuf) { assert((gc == NULL && gcbuf == NULL) || (gc != NULL && gcbuf != NULL)); Geometry::wkbType geotype= geom->get_type(); switch (geotype) { case Geometry::wkb_point: { typename BG_models::Point geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()), out; boost::geometry::simplify(geo, out, max_dist); if ((null_value= post_fix_result(&bg_resbuf_mgr, out, str))) return null_value; if (gc && (null_value= gc->append_geometry(&out, gcbuf))) return null_value; } break; case Geometry::wkb_multipoint: { typename BG_models::Multipoint geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()), out; boost::geometry::simplify(geo, out, max_dist); if ((null_value= post_fix_result(&bg_resbuf_mgr, out, str))) return null_value; if (gc && (null_value= gc->append_geometry(&out, gcbuf))) return null_value; } break; case Geometry::wkb_linestring: { typename BG_models::Linestring geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()), out; boost::geometry::simplify(geo, out, max_dist); if ((null_value= post_fix_result(&bg_resbuf_mgr, out, str))) return null_value; if (gc && (null_value= gc->append_geometry(&out, gcbuf))) return null_value; } break; case Geometry::wkb_multilinestring: { typename BG_models::Multilinestring geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()), out; boost::geometry::simplify(geo, out, max_dist); if ((null_value= post_fix_result(&bg_resbuf_mgr, out, str))) return null_value; if (gc && (null_value= gc->append_geometry(&out, gcbuf))) return null_value; } break; case Geometry::wkb_polygon: { typename BG_models::Polygon geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()), out; boost::geometry::simplify(geo, out, max_dist); if ((null_value= post_fix_result(&bg_resbuf_mgr, out, str))) return null_value; if (gc && (null_value= gc->append_geometry(&out, gcbuf))) return null_value; } break; case Geometry::wkb_multipolygon: { typename BG_models::Multipolygon geo(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()), out; boost::geometry::simplify(geo, out, max_dist); if ((null_value= post_fix_result(&bg_resbuf_mgr, out, str))) return null_value; if (gc && (null_value= gc->append_geometry(&out, gcbuf))) return null_value; } break; case Geometry::wkb_geometrycollection: default: assert(false); break; } return 0; } /* Spatial decomposition functions */ String *Item_func_spatial_decomp::val_str(String *str) { assert(fixed == 1); String arg_val; String *swkb= args[0]->val_str(&arg_val); Geometry_buffer buffer; Geometry *geom= NULL; uint32 srid; if ((null_value= (!swkb || args[0]->null_value))) return NULL; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } srid= uint4korr(swkb->ptr()); str->set_charset(&my_charset_bin); if (str->reserve(SRID_SIZE, 512)) goto err; str->length(0); str->q_append(srid); switch (decomp_func) { case SP_STARTPOINT: if (geom->start_point(str)) goto err; break; case SP_ENDPOINT: if (geom->end_point(str)) goto err; break; case SP_EXTERIORRING: if (geom->exterior_ring(str)) goto err; break; default: goto err; } return str; err: null_value= 1; return 0; } String *Item_func_spatial_decomp_n::val_str(String *str) { assert(fixed == 1); String arg_val; String *swkb= args[0]->val_str(&arg_val); long n= (long) args[1]->val_int(); Geometry_buffer buffer; Geometry *geom= NULL; uint32 srid; if ((null_value= (!swkb || args[0]->null_value || args[1]->null_value))) return NULL; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } str->set_charset(&my_charset_bin); if (str->reserve(SRID_SIZE, 512)) goto err; srid= uint4korr(swkb->ptr()); str->length(0); str->q_append(srid); switch (decomp_func_n) { case SP_POINTN: if (geom->point_n(n,str)) goto err; break; case SP_GEOMETRYN: if (geom->geometry_n(n,str)) goto err; break; case SP_INTERIORRINGN: if (geom->interior_ring_n(n,str)) goto err; break; default: goto err; } return str; err: null_value=1; return 0; } /* Functions to concatenate various spatial objects */ /* * Concatenate doubles into Point */ Field::geometry_type Item_func_point::get_geometry_type() const { return Field::GEOM_POINT; } String *Item_func_point::val_str(String *str) { assert(fixed == 1); /* The coordinates of a point can't be another geometry, but other types are allowed as before. */ if ((null_value= (args[0]->field_type() == MYSQL_TYPE_GEOMETRY || args[1]->field_type() == MYSQL_TYPE_GEOMETRY))) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); return error_str(); } double x= args[0]->val_real(); double y= args[1]->val_real(); uint32 srid= 0; if ((null_value= (args[0]->null_value || args[1]->null_value || str->mem_realloc(4/*SRID*/ + 1 + 4 + SIZEOF_STORED_DOUBLE * 2)))) return 0; str->set_charset(&my_charset_bin); str->length(0); str->q_append(srid); str->q_append((char)Geometry::wkb_ndr); str->q_append((uint32)Geometry::wkb_point); str->q_append(x); str->q_append(y); return str; } /// This will check if arguments passed (geohash and SRID) are of valid types. bool Item_func_pointfromgeohash::fix_fields(THD *thd, Item **ref) { if (Item_geometry_func::fix_fields(thd, ref)) return true; maybe_null= (args[0]->maybe_null || args[1]->maybe_null); // Check for valid type in geohash argument. if (!Item_func_latlongfromgeohash::check_geohash_argument_valid_type(args[0])) { my_error(ER_INCORRECT_TYPE, MYF(0), "geohash", func_name()); return true; } /* Check for valid type in SRID argument. We will allow all integer types, and strings since some connectors will covert integers to strings. Binary data is not allowed. PARAM_ITEM and INT_ITEM checks are to allow prepared statements and usage of user-defined variables respectively. */ if (Item_func_geohash::is_item_null(args[1])) return false; if (args[1]->collation.collation == &my_charset_bin && args[1]->type() != PARAM_ITEM && args[1]->type() != INT_ITEM) { my_error(ER_INCORRECT_TYPE, MYF(0), "SRID", func_name()); return true; } switch (args[1]->field_type()) { case MYSQL_TYPE_STRING: case MYSQL_TYPE_VARCHAR: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_INT24: case MYSQL_TYPE_LONG: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_TINY: break; default: my_error(ER_INCORRECT_TYPE, MYF(0), "SRID", func_name()); return true; } return false; } String *Item_func_pointfromgeohash::val_str(String *str) { assert(fixed == TRUE); String argument_value; String *geohash= args[0]->val_str_ascii(&argument_value); longlong srid_input= args[1]->val_int(); // Return null if one or more of the input arguments is null. if ((null_value= (args[0]->null_value || args[1]->null_value))) return NULL; // Only allow unsigned 32 bits integer as SRID. if (srid_input < 0 || srid_input > UINT_MAX32) { char srid_string[MAX_BIGINT_WIDTH + 1]; llstr(srid_input, srid_string); my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "SRID", srid_string, func_name()); return error_str(); } if (str->mem_realloc(GEOM_HEADER_SIZE + POINT_DATA_SIZE)) return make_empty_result(); if (geohash->length() == 0) { my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "geohash", geohash->c_ptr_safe(), func_name()); return error_str(); } double latitude= 0.0; double longitude= 0.0; uint32 srid= static_cast(srid_input); if (Item_func_latlongfromgeohash::decode_geohash(geohash, upper_latitude, lower_latitude, upper_longitude, lower_longitude, &latitude, &longitude)) { my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "geohash", geohash->c_ptr_safe(), func_name()); return error_str(); } str->set_charset(&my_charset_bin); str->length(0); write_geometry_header(str, srid, Geometry::wkb_point); str->q_append(longitude); str->q_append(latitude); return str; } const char *Item_func_spatial_collection::func_name() const { const char *str= NULL; switch (coll_type) { case Geometry::wkb_multipoint: str= "multipoint"; break; case Geometry::wkb_multilinestring: str= "multilinestring"; break; case Geometry::wkb_multipolygon: str= "multipolygon"; break; case Geometry::wkb_linestring: str= "linestring"; break; case Geometry::wkb_polygon: str= "polygon"; break; case Geometry::wkb_geometrycollection: str= "geometrycollection"; break; default: assert(false); break; } return str; } /** Concatenates various items into various collections with checkings for valid wkb type of items. For example, multipoint can be a collection of points only. coll_type contains wkb type of target collection. item_type contains a valid wkb type of items. In the case when coll_type is wkbGeometryCollection, we do not check wkb type of items, any is valid. */ String *Item_func_spatial_collection::val_str(String *str) { assert(fixed == 1); String arg_value; uint i; uint32 srid= 0; str->set_charset(&my_charset_bin); str->length(0); if (str->reserve(4/*SRID*/ + 1 + 4 + 4, 512)) goto err; str->q_append(srid); str->q_append((char) Geometry::wkb_ndr); str->q_append((uint32) coll_type); str->q_append((uint32) arg_count); // We can construct an empty geometry by calling GeometryCollection(). if (arg_count == 0) return str; for (i= 0; i < arg_count; ++i) { String *res= args[i]->val_str(&arg_value); size_t len; if (args[i]->null_value || ((len= res->length()) < WKB_HEADER_SIZE)) goto err; if (coll_type == Geometry::wkb_geometrycollection) { /* In the case of GeometryCollection we don't need any checkings for item types, so just copy them into target collection */ if (str->append(res->ptr() + 4/*SRID*/, len - 4/*SRID*/, (uint32) 512)) goto err; } else { enum Geometry::wkbType wkb_type; const uint data_offset= 4/*SRID*/ + 1; if (res->length() < data_offset + sizeof(uint32)) goto err; const char *data= res->ptr() + data_offset; /* In the case of named collection we must check that items are of specific type, let's do this checking now */ wkb_type= get_wkb_geotype(data); data+= 4; len-= 5 + 4/*SRID*/; if (wkb_type != item_type) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); goto err; } switch (coll_type) { case Geometry::wkb_multipoint: case Geometry::wkb_multilinestring: case Geometry::wkb_multipolygon: if (len < WKB_HEADER_SIZE || str->append(data-WKB_HEADER_SIZE, len+WKB_HEADER_SIZE, 512)) goto err; break; case Geometry::wkb_linestring: if (len < POINT_DATA_SIZE || str->append(data, POINT_DATA_SIZE, 512)) goto err; break; case Geometry::wkb_polygon: { uint32 n_points; double x1, y1, x2, y2; const char *org_data= data; if (len < 4) goto err; n_points= uint4korr(data); data+= 4; // A ring must have at least 4 points. if (n_points < 4 || len != 4 + n_points * POINT_DATA_SIZE) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } float8get(&x1, data); data+= SIZEOF_STORED_DOUBLE; float8get(&y1, data); data+= SIZEOF_STORED_DOUBLE; data+= (n_points - 2) * POINT_DATA_SIZE; float8get(&x2, data); float8get(&y2, data + SIZEOF_STORED_DOUBLE); // A ring must be closed. if ((x1 != x2) || (y1 != y2)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } if (str->append(org_data, len, 512)) goto err; } break; default: goto err; } } } if (str->length() > current_thd->variables.max_allowed_packet) { push_warning_printf(current_thd, Sql_condition::SL_WARNING, ER_WARN_ALLOWED_PACKET_OVERFLOWED, ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(), current_thd->variables.max_allowed_packet); goto err; } if (coll_type == Geometry::wkb_linestring && arg_count < 2) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } /* The construct() call parses the string to make sure it's a valid WKB byte string instead of some arbitrary trash bytes. Above code assumes so and doesn't further completely validate the string's content. There are several goto statements above so we have to construct the geom_buff object in a scope, this is more of C++ style than defining it at start of this function. */ { Geometry_buffer geom_buff; if (Geometry::construct(&geom_buff, str) == NULL) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_str(); } } null_value= 0; return str; err: null_value= 1; return 0; } /** Convert this into a Gis_geometry_collection object. @param geodata Stores the result object's WKB data. @return The Gis_geometry_collection object created from this object. */ Gis_geometry_collection * BG_geometry_collection::as_geometry_collection(String *geodata) const { if (m_geos.size() == 0) return empty_collection(geodata, m_srid); Gis_geometry_collection *gc= NULL; for (Geometry_list::const_iterator i= m_geos.begin(); i != m_geos.end(); ++i) { if (gc == NULL) gc= new Gis_geometry_collection(*i, geodata); else gc->append_geometry(*i, geodata); } return gc; } /** Store a Geometry object into this collection. If it's a geometry collection, flatten it and store its components into this collection, so that no component is a geometry collection. @param geo The Geometry object to put into this collection. We duplicate geo's data rather than directly using it. @param break_multi_geom whether break a multipoint or multilinestring or multipolygon so as to store its components separately into this object. @return true if error occured, false if no error(successful). */ bool BG_geometry_collection::store_geometry(const Geometry *geo, bool break_multi_geom) { Geometry::wkbType geo_type= geo->get_type(); if ((geo_type == Geometry::wkb_geometrycollection) || (break_multi_geom && (geo_type == Geometry::wkb_multipoint || geo_type == Geometry::wkb_multipolygon || geo_type == Geometry::wkb_multilinestring))) { uint32 ngeom= 0; if (geo->num_geometries(&ngeom)) return true; /* Get its components and store each of them separately, if a component is also a collection, recursively disintegrate and store its components in the same way. */ for (uint32 i= 1; i <= ngeom; i++) { String *pres= m_geosdata.append_object(); if (pres == NULL || pres->reserve(GEOM_HEADER_SIZE, 512)) return true; pres->q_append(geo->get_srid()); if (geo->geometry_n(i, pres)) return true; Geometry_buffer *pgeobuf= m_geobufs.append_object(); if (pgeobuf == NULL) return true; Geometry *geo2= Geometry::construct(pgeobuf, pres->ptr(), pres->length()); if (geo2 == NULL) { // The geometry data already pass such checks, it's always valid here. assert(false); return true; } else if (geo2->get_type() == Geometry::wkb_geometrycollection) { if (store_geometry(geo2, break_multi_geom)) return true; } else { geo2->has_geom_header_space(true); m_geos.push_back(geo2); } } /* GCs with no-overlapping components can only be returned by combine_sub_results, which combines geometries from BG set operations, so no nested GCs or other user defined GCs are really set to true here. */ set_comp_no_overlapped(geo->is_components_no_overlapped() || ngeom == 1); } else if (store(geo) == NULL) return true; return false; } /** Store a geometry of GEOMETRY format into this collection. @param geo a geometry object whose data of GEOMETRY format is to be duplicated and stored into this collection. It's not a geometry collection. @return a duplicated Geometry object created from geo. */ Geometry *BG_geometry_collection::store(const Geometry *geo) { String *pres= NULL; Geometry *geo2= NULL; Geometry_buffer *pgeobuf= NULL; size_t geosize= geo->get_data_size(); assert(geo->get_type() != Geometry::wkb_geometrycollection); pres= m_geosdata.append_object(); if (pres == NULL || pres->reserve(GEOM_HEADER_SIZE + geosize, 256)) return NULL; write_geometry_header(pres, geo->get_srid(), geo->get_type()); pres->q_append(geo->get_cptr(), geosize); pgeobuf= m_geobufs.append_object(); if (pgeobuf == NULL) return NULL; geo2= Geometry::construct(pgeobuf, pres->ptr(), pres->length()); // The geometry data already pass such checks, it's always valid here. assert(geo2 != NULL); if (geo2 != NULL && geo2->get_type() != Geometry::wkb_geometrycollection) m_geos.push_back(geo2); return geo2; } longlong Item_func_isempty::val_int() { assert(fixed == 1); String tmp; String *swkb= args[0]->val_str(&tmp); Geometry_buffer buffer; Geometry *g= NULL; if ((null_value= (!swkb || args[0]->null_value))) return 0; if (!(g= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_int(); } return (null_value || is_empty_geocollection(g)) ? 1 : 0; } longlong Item_func_issimple::val_int() { DBUG_ENTER("Item_func_issimple::val_int"); assert(fixed == 1); tmp.length(0); String *arg_wkb= args[0]->val_str(&tmp); if ((null_value= args[0]->null_value)) { assert(maybe_null); DBUG_RETURN(0); } if (arg_wkb == NULL) { // Invalid geometry. my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); DBUG_RETURN(error_int()); } Geometry_buffer buffer; Geometry *arg= Geometry::construct(&buffer, arg_wkb); if (arg == NULL) { // Invalid geometry. my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); DBUG_RETURN(error_int()); } DBUG_RETURN(issimple(arg)); } /** Evaluate if a geometry object is simple according to the OGC definition. @param g The geometry to evaluate. @return True if the geometry is simple, false otherwise. */ bool Item_func_issimple::issimple(Geometry *g) { bool res= false; try { switch (g->get_type()) { case Geometry::wkb_point: { Gis_point arg(g->get_data_ptr(), g->get_data_size(), g->get_flags(), g->get_srid()); res= boost::geometry::is_simple(arg); } break; case Geometry::wkb_linestring: { Gis_line_string arg(g->get_data_ptr(), g->get_data_size(), g->get_flags(), g->get_srid()); res= boost::geometry::is_simple(arg); } break; case Geometry::wkb_polygon: { const void *arg_wkb= g->normalize_ring_order(); if (arg_wkb == NULL) { // Invalid polygon. my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_bool(); } Gis_polygon arg(arg_wkb, g->get_data_size(), g->get_flags(), g->get_srid()); res= boost::geometry::is_simple(arg); } break; case Geometry::wkb_multipoint: { Gis_multi_point arg(g->get_data_ptr(), g->get_data_size(), g->get_flags(), g->get_srid()); res= boost::geometry::is_simple(arg); } break; case Geometry::wkb_multilinestring: { Gis_multi_line_string arg(g->get_data_ptr(), g->get_data_size(), g->get_flags(), g->get_srid()); res= boost::geometry::is_simple(arg); } break; case Geometry::wkb_multipolygon: { const void *arg_wkb= g->normalize_ring_order(); if (arg_wkb == NULL) { // Invalid multipolygon. my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_bool(); } Gis_multi_polygon arg(arg_wkb, g->get_data_size(), g->get_flags(), g->get_srid()); res= boost::geometry::is_simple(arg); } break; case Geometry::wkb_geometrycollection: { BG_geometry_collection collection; collection.fill(g); res= true; for (BG_geometry_collection::Geometry_list::iterator i= collection.get_geometries().begin(); i != collection.get_geometries().end(); ++i) { res= issimple(*i); if (current_thd->is_error()) { res= error_bool(); break; } if (!res) { break; } } } break; default: assert(0); break; } } catch (...) { res= error_bool(); handle_gis_exception(func_name()); } return res; } longlong Item_func_isclosed::val_int() { assert(fixed == 1); String tmp; String *swkb= args[0]->val_str(&tmp); Geometry_buffer buffer; Geometry *geom; int isclosed= 0; // In case of error if ((null_value= (!swkb || args[0]->null_value))) return 0L; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_int(); } null_value= geom->is_closed(&isclosed); return (longlong) isclosed; } /** Checks the validity of a geometry collection, which is valid if and only if all its components are valid. */ class Geomcoll_validity_checker: public WKB_scanner_event_handler { bool m_isvalid; Geometry::srid_t m_srid; std::stack types; public: explicit Geomcoll_validity_checker(Geometry::srid_t srid) :m_isvalid(true), m_srid(srid) { } bool is_valid() const { return m_isvalid; } virtual void on_wkb_start(Geometry::wkbByteOrder bo, Geometry::wkbType geotype, const void *wkb, uint32 len, bool has_hdr) { if (!m_isvalid) return; Geometry::wkbType top= Geometry::wkb_invalid_type; if (types.size() > 0) top= types.top(); else assert(geotype == Geometry::wkb_geometrycollection); types.push(geotype); // A geometry collection's vaidity is determined by that of its components. if (geotype == Geometry::wkb_geometrycollection) return; // If not owned by a GC(i.e. not a direct component of a GC), it doesn't // influence the GC's validity. if (top != Geometry::wkb_geometrycollection) return; assert(top != Geometry::wkb_invalid_type && has_hdr); assert(len > WKB_HEADER_SIZE); Geometry_buffer geobuf; Geometry *geo= NULL; // Provide the WKB header starting address, wkb MUST have a WKB header // right before it. geo= Geometry::construct(&geobuf, static_cast(wkb) - WKB_HEADER_SIZE, len + WKB_HEADER_SIZE, false/* has no srid */); if (geo == NULL) m_isvalid= false; else { geo->set_srid(m_srid); m_isvalid= check_geometry_valid(geo); } } virtual void on_wkb_end(const void *wkb) { if (types.size() > 0) types.pop(); } }; /* Call Boost Geometry algorithm to check whether a geometry is valid as defined by OGC. */ static int check_geometry_valid(Geometry *geom) { int ret= 0; // Empty geometry collection is always valid. This is shortcut for // flat empty GCs. if (is_empty_geocollection(geom)) return 1; switch (geom->get_type()) { case Geometry::wkb_point: { BG_models::Point bg(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); ret= bg::is_valid(bg); break; } case Geometry::wkb_linestring: { BG_models::Linestring bg(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); ret= bg::is_valid(bg); break; } case Geometry::wkb_polygon: { const void *ptr= geom->normalize_ring_order(); if (ptr == NULL) { ret= 0; break; } BG_models::Polygon bg(ptr, geom->get_data_size(), geom->get_flags(), geom->get_srid()); ret= bg::is_valid(bg); break; } case Geometry::wkb_multipoint: { BG_models::Multipoint bg(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); ret= bg::is_valid(bg); break; } case Geometry::wkb_multilinestring: { BG_models::Multilinestring bg(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); ret= bg::is_valid(bg); break; } case Geometry::wkb_multipolygon: { const void *ptr= geom->normalize_ring_order(); if (ptr == NULL) { ret= 0; break; } BG_models::Multipolygon bg(ptr, geom->get_data_size(), geom->get_flags(), geom->get_srid()); ret= bg::is_valid(bg); break; } case Geometry::wkb_geometrycollection: { uint32 wkb_len= geom->get_data_size(); Geomcoll_validity_checker validity_checker(geom->get_srid()); /* This case can only be reached when this function isn't recursively called (indirectly by itself) but called by other functions, and it's the WKB data doesn't have a WKB header before it. Otherwise in Geomcoll_validity_checker it's required that the WKB data has a header. */ wkb_scanner(geom->get_cptr(), &wkb_len, Geometry::wkb_geometrycollection, false, &validity_checker); ret= validity_checker.is_valid(); break; } default: assert(false); break; } return ret; } longlong Item_func_isvalid::val_int() { assert(fixed == 1); String tmp; String *swkb= args[0]->val_str(&tmp); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return 0L; // It should return false if the argument isn't a valid GEOMETRY string. if (!(geom= Geometry::construct(&buffer, swkb))) return 0L; if (geom->get_srid() != 0) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); return error_int(); } int ret= 0; try { ret= check_geometry_valid(geom); } catch (...) { null_value= true; handle_gis_exception("ST_IsValid"); } return ret; } /* Numerical functions */ longlong Item_func_dimension::val_int() { assert(fixed == 1); uint32 dim= 0; // In case of error String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return 0; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_int(); } null_value= geom->dimension(&dim); return (longlong) dim; } longlong Item_func_numinteriorring::val_int() { assert(fixed == 1); uint32 num= 0; // In case of error String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return 0L; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_int(); } null_value= geom->num_interior_ring(&num); return (longlong) num; } longlong Item_func_numgeometries::val_int() { assert(fixed == 1); uint32 num= 0; // In case of errors String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return 0L; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_int(); } null_value= geom->num_geometries(&num); return (longlong) num; } longlong Item_func_numpoints::val_int() { assert(fixed == 1); uint32 num= 0; // In case of errors String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return 0L; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_int(); } null_value= geom->num_points(&num); return (longlong) num; } double Item_func_x::val_real() { assert(fixed == 1); double res= 0.0; // In case of errors String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return res; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } null_value= geom->get_x(&res); return res; } double Item_func_y::val_real() { assert(fixed == 1); double res= 0; // In case of errors String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return res; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } null_value= geom->get_y(&res); return res; } template double Item_func_area::bg_area(const Geometry *geom) { double res= 0; try { switch (geom->get_type()) { case Geometry::wkb_point: case Geometry::wkb_multipoint: case Geometry::wkb_linestring: case Geometry::wkb_multilinestring: res= 0; break; case Geometry::wkb_polygon: { typename BG_models::Polygon plgn(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); res= boost::geometry::area(plgn); } break; case Geometry::wkb_multipolygon: { typename BG_models::Multipolygon mplgn(geom->get_data_ptr(), geom->get_data_size(), geom->get_flags(), geom->get_srid()); res= boost::geometry::area(mplgn); } break; case Geometry::wkb_geometrycollection: { BG_geometry_collection bggc; bggc.fill(geom); for (BG_geometry_collection::Geometry_list::iterator i= bggc.get_geometries().begin(); i != bggc.get_geometries().end(); ++i) { if ((*i)->get_geotype() != Geometry::wkb_geometrycollection && (*i)->normalize_ring_order() == NULL) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); null_value= true; return 0; } res+= bg_area(*i); if (null_value) return res; } } break; default: assert(false); break; } } catch (...) { null_value= true; handle_gis_exception("st_area"); } /* Given a polygon whose rings' points are in counter-clockwise order, boost geometry computes an area of negative value. Also, the inner ring has to be clockwise. We now always make polygon rings CCW --- outer ring CCW and inner rings CW, thus if we get a negative value, it's because the inner ring is larger than the outer ring, and we should keep it negative. */ return res; } double Item_func_area::val_real() { assert(fixed == 1); double res= 0; // In case of errors String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return res; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } assert(geom->get_coordsys() == Geometry::cartesian); if (geom->get_geotype() != Geometry::wkb_geometrycollection && geom->normalize_ring_order() == NULL) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } res= bg_area(geom); // Had error in bg_area. if (null_value) return error_real(); if (!my_isfinite(res)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } return res; } double Item_func_glength::val_real() { assert(fixed == 1); double res= 0; // In case of errors String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; Geometry *geom; if ((null_value= (!swkb || args[0]->null_value))) return res; if (!(geom= Geometry::construct(&buffer, swkb))) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } if ((null_value= geom->geom_length(&res))) return res; if (!my_isfinite(res)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } return res; } longlong Item_func_srid::val_int() { assert(fixed == 1); String *swkb= args[0]->val_str(&value); Geometry_buffer buffer; longlong res= 0L; if ((null_value= (!swkb || args[0]->null_value))) return res; if (!Geometry::construct(&buffer, swkb)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_int(); } return (longlong) (uint4korr(swkb->ptr())); } /** Compact a geometry collection, making all its points/multipoints into a single multipoint object, and all its linestrings/multilinestrings into a single multilinestring object; leave polygons and multipolygons as they were. @param g the input geometry collection. @param gbuf the place to create the new result geometry collection. @param str the String buffer to hold data of the result geometry collection. */ static const Geometry * compact_collection(const Geometry *g, Geometry_buffer *gbuf, String *str) { if (g->get_geotype() != Geometry::wkb_geometrycollection) return g; uint32 wkb_len, wkb_len0; char *wkb_start= g->get_cptr(); wkb_len= wkb_len0= g->get_data_size(); BG_models::Multilinestring mls; Geometry_grouper::Linestring> ls_grouper(&mls); wkb_scanner(wkb_start, &wkb_len, Geometry::wkb_geometrycollection, false, &ls_grouper); BG_models::Multipoint mpts; wkb_len= wkb_len0; Geometry_grouper::Point> pt_grouper(&mpts); wkb_scanner(wkb_start, &wkb_len, Geometry::wkb_geometrycollection, false, &pt_grouper); Gis_geometry_collection *ret= new (gbuf) Gis_geometry_collection(); wkb_len= wkb_len0; Geometry_grouper::Polygon> mplgn_grouper(ret, str); wkb_scanner(wkb_start, &wkb_len, Geometry::wkb_geometrycollection, false, &mplgn_grouper); ret->append_geometry(&mls, str); ret->append_geometry(&mpts, str); return ret; } template class Numeric_interval { Num_type left, right; bool is_left_open, is_right_open; bool is_left_unlimitted, is_right_unlimitted; public: // Construct a boundless interval. Numeric_interval() :left(0), right(0), is_left_open(false), is_right_open(false), is_left_unlimitted(true), is_right_unlimitted(true) { } // Construct a both bounded internal. Numeric_interval(const Num_type &l, bool left_open, const Num_type &r, bool right_open) :left(l), right(r), is_left_open(left_open), is_right_open(right_open), is_left_unlimitted(false), is_right_unlimitted(false) { } // Construct a half boundless half bounded interval. // @param is_left true to specify the left boundary and right is boundless; // false to specify the right boundary and left is boundless. Numeric_interval(bool is_left, const Num_type &v, bool is_open) :left(0), right(0), is_left_open(false), is_right_open(false), is_left_unlimitted(true), is_right_unlimitted(true) { if (is_left) { left= v; is_left_open= is_open; is_left_unlimitted= false; } else { right= v; is_right_open= is_open; is_right_unlimitted= false; } } /* Get left boundary specification. @param [out] l takes back left boundary value. if boundless it's intact. @param [out] is_open takes back left boundary openness. if boundless it's intact. @return true if it's boundless, false if bounded and the two out parameters take back the boundary info. */ bool get_left(Num_type &l, bool &is_open) const { if (is_left_unlimitted) return true; l= left; is_open= is_left_open; return false; } /* Get right boundary specification. @param [out] r takes back right boundary value. if boundless it's intact. @param [out] is_open takes back right boundary openness. if boundless it's intact. @return true if it's boundless, false if bounded and the two out parameters take back the boundary info. */ bool get_right(Num_type &r, bool &is_open) const { if (is_right_unlimitted) return true; r= right; is_open= is_right_open; return false; } }; class Point_coordinate_checker : public WKB_scanner_event_handler { bool has_invalid; // Valid x and y coordinate range. Numeric_interval x_val, y_val; public: Point_coordinate_checker(Numeric_interval x_range, Numeric_interval y_range) :has_invalid(false), x_val(x_range), y_val(y_range) {} virtual void on_wkb_start(Geometry::wkbByteOrder bo, Geometry::wkbType geotype, const void *wkb, uint32 len, bool has_hdr) { if (geotype == Geometry::wkb_point) { Gis_point pt(wkb, POINT_DATA_SIZE, Geometry::Flags_t(Geometry::wkb_point, len), 0); double x= pt.get<0>(), y= pt.get<1>(); double xmin, xmax, ymin, ymax; bool xmin_open, ymin_open, xmax_open, ymax_open; if ((!x_val.get_left(xmin, xmin_open) && (x < xmin || (xmin_open && x == xmin))) || (!x_val.get_right(xmax, xmax_open) && (x > xmax || (xmax_open && x == xmax))) || (!y_val.get_left(ymin, ymin_open) && (y < ymin || (ymin_open && y == ymin))) || (!y_val.get_right(ymax, ymax_open) && (y > ymax || (ymax_open && y == ymax)))) { has_invalid= true; return; } } } virtual void on_wkb_end(const void *wkb) { } bool has_invalid_point() const { return has_invalid; } }; template struct BG_distance {}; template <> struct BG_distance { static double apply(Item_func_distance *item, Geometry *g1, Geometry *g2) { // Do the actual computation here for the cartesian CS. return item->bg_distance(g1, g2); } }; template <> struct BG_distance > { static double apply(Item_func_distance *item, Geometry *g1, Geometry *g2) { // Do the actual computation here for the spherical equatorial CS // with degree units. return item->bg_distance_spherical(g1, g2); } }; double Item_func_distance::val_real() { typedef bgcs::spherical_equatorial bgcssed; double distance= 0; DBUG_ENTER("Item_func_distance::val_real"); assert(fixed == 1); String *res1= args[0]->val_str(&tmp_value1); String *res2= args[1]->val_str(&tmp_value2); Geometry_buffer buffer1, buffer2; Geometry *g1, *g2; if ((null_value= (!res1 || args[0]->null_value || !res2 || args[1]->null_value))) DBUG_RETURN(0.0); if (!(g1= Geometry::construct(&buffer1, res1)) || !(g2= Geometry::construct(&buffer2, res2))) { // If construction fails, we assume invalid input data. my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); DBUG_RETURN(error_real()); } // The two geometry operand must be in the same coordinate system. if (g1->get_srid() != g2->get_srid()) { my_error(ER_GIS_DIFFERENT_SRIDS, MYF(0), func_name(), g1->get_srid(), g2->get_srid()); DBUG_RETURN(error_real()); } if ((g1->get_geotype() != Geometry::wkb_geometrycollection && g1->normalize_ring_order() == NULL) || (g2->get_geotype() != Geometry::wkb_geometrycollection && g2->normalize_ring_order() == NULL)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); DBUG_RETURN(error_real()); } if (is_spherical_equatorial) { Geometry::wkbType gt1= g1->get_geotype(); Geometry::wkbType gt2= g2->get_geotype(); if (!((gt1 == Geometry::wkb_point || gt1 == Geometry::wkb_multipoint) && (gt2 == Geometry::wkb_point || gt2 == Geometry::wkb_multipoint))) { my_error(ER_GIS_UNSUPPORTED_ARGUMENT, MYF(0), func_name()); DBUG_RETURN(error_real()); } if (arg_count == 3) { earth_radius= args[2]->val_real(); if (args[2]->null_value) DBUG_RETURN(error_real()); if (earth_radius <= 0) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); DBUG_RETURN(error_real()); } } /* Make sure all points' coordinates are valid: x in (-180, 180], y in [-90, 90]. */ Numeric_interval x_range(-180, true, 180, false); // (-180, 180] Numeric_interval y_range(-90, false, 90, false); // [-90, 90] Point_coordinate_checker checker(x_range, y_range); uint32 wkblen= res1->length() - 4; wkb_scanner(res1->ptr() + 4, &wkblen, Geometry::wkb_invalid_type, true, &checker); if (checker.has_invalid_point()) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); DBUG_RETURN(error_real()); } wkblen= res2->length() - 4; wkb_scanner(res2->ptr() + 4, &wkblen, Geometry::wkb_invalid_type, true, &checker); if (checker.has_invalid_point()) { my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name()); DBUG_RETURN(error_real()); } } if (g1->get_type() != Geometry::wkb_geometrycollection && g2->get_type() != Geometry::wkb_geometrycollection) { if (is_spherical_equatorial) distance= BG_distance::apply(this, g1, g2); else distance= BG_distance::apply(this, g1, g2); } else distance= geometry_collection_distance(g1, g2); if (null_value) DBUG_RETURN(error_real()); if (!my_isfinite(distance) || distance < 0) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); DBUG_RETURN(error_real()); } DBUG_RETURN(distance); } /* Calculate the distance of two geometry collections. BG has optimized algorithm to calculate distance among multipoints, multilinestrings and polygons, so we compact the collection to make a single multipoint, a single multilinestring, and the rest are all polygons and multipolygons, and do a nested loop to calculate the minimum distances among such compacted components as the final result. */ double Item_func_distance:: geometry_collection_distance(const Geometry *g1, const Geometry *g2) { BG_geometry_collection bggc1, bggc2; bool initialized= false, all_normalized= false; double min_distance= DBL_MAX, dist= DBL_MAX; String gcstr1, gcstr2; Geometry_buffer buf1, buf2; const Geometry *g11, *g22; g11= compact_collection(g1, &buf1, &gcstr1); g22= compact_collection(g2, &buf2, &gcstr2); bggc1.fill(g11); bggc2.fill(g22); for (BG_geometry_collection::Geometry_list::iterator i= bggc1.get_geometries().begin(); i != bggc1.get_geometries().end(); ++i) { /* Normalize polygon rings, do only once for each component. */ if ((*i)->get_geotype() != Geometry::wkb_geometrycollection && (*i)->normalize_ring_order() == NULL) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } for (BG_geometry_collection::Geometry_list::iterator j= bggc2.get_geometries().begin(); j != bggc2.get_geometries().end(); ++j) { /* Normalize polygon rings, do only once for each component. */ if (!all_normalized && (*j)->get_geotype() != Geometry::wkb_geometrycollection && (*j)->normalize_ring_order() == NULL) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name()); return error_real(); } if (is_spherical_equatorial) { // For now this is never reached because we only support // distance([multi]point, [multi]point) for spherical. assert(false); } else dist= BG_distance::apply(this, *i, *j); if (null_value) return error_real(); if (dist < 0 || boost::math::isnan(dist)) return dist; if (!initialized) { min_distance= dist; initialized= true; } else if (min_distance > dist) min_distance= dist; } all_normalized= true; if (!initialized) break; // bggc2 is empty. } /* If at least one of the collections is empty, we have NULL result. */ if (!initialized) return error_real(); return min_distance; } double Item_func_distance:: distance_point_geometry_spherical(const Geometry *g1, const Geometry *g2) { typedef bgcs::spherical_equatorial bgcssed; double res= 0; bg::strategy::distance::haversine dist_strategy(earth_radius); BG_models::Point bg1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(), g1->get_srid()); switch (g2->get_type()) { case Geometry::wkb_point: { BG_models::Point bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2, dist_strategy); } break; case Geometry::wkb_multipoint: { BG_models::Multipoint bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2, dist_strategy); } break; default: assert(false); break; } return res; } double Item_func_distance:: distance_multipoint_geometry_spherical(const Geometry *g1, const Geometry *g2) { typedef bgcs::spherical_equatorial bgcssed; double res= 0; bg::strategy::distance::haversine dist_strategy(earth_radius); BG_models::Multipoint bg1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(), g1->get_srid()); switch (g2->get_type()) { case Geometry::wkb_point: { BG_models::Point bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2, dist_strategy); } break; case Geometry::wkb_multipoint: { BG_models::Multipoint bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2, dist_strategy); } break; default: assert(false); break; } return res; } double Item_func_distance:: bg_distance_spherical(const Geometry *g1, const Geometry *g2) { double res= 0; try { switch (g1->get_type()) { case Geometry::wkb_point: res= distance_point_geometry_spherical(g1, g2); break; case Geometry::wkb_multipoint: res= distance_multipoint_geometry_spherical(g1, g2); break; default: assert(false); break; } } catch (...) { null_value= true; handle_gis_exception("st_distance_sphere"); } return res; } template double Item_func_distance:: distance_dispatch_second_geometry(const BG_geometry& bg1, const Geometry* g2) { double res= 0; switch (g2->get_type()) { case Geometry::wkb_point: { typename BG_models::Point bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2); } break; case Geometry::wkb_multipoint: { typename BG_models::Multipoint bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2); } break; case Geometry::wkb_linestring: { typename BG_models::Linestring bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2); } break; case Geometry::wkb_multilinestring: { typename BG_models::Multilinestring bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2); } break; case Geometry::wkb_polygon: { typename BG_models::Polygon bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2); } break; case Geometry::wkb_multipolygon: { typename BG_models::Multipolygon bg2(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(), g2->get_srid()); res= bg::distance(bg1, bg2); } break; default: assert(false); break; } return res; } /* Calculate distance of g1 and g2 using Boost.Geometry. We split the implementation into 6 smaller functions according to the type of g1, to make all functions smaller in size. Because distance is symmetric, we swap parameters if the swapped type combination is already implemented. */ template double Item_func_distance::bg_distance(const Geometry *g1, const Geometry *g2) { double res= 0; bool had_except= false; try { switch (g1->get_type()) { case Geometry::wkb_point: { typename BG_models::Point bg1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(), g1->get_srid()); res= distance_dispatch_second_geometry(bg1, g2); } break; case Geometry::wkb_multipoint: { typename BG_models::Multipoint bg1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(), g1->get_srid()); res= distance_dispatch_second_geometry(bg1, g2); } break; case Geometry::wkb_linestring: { typename BG_models::Linestring bg1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(), g1->get_srid()); res= distance_dispatch_second_geometry(bg1, g2); } break; case Geometry::wkb_multilinestring: { typename BG_models::Multilinestring bg1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(), g1->get_srid()); res= distance_dispatch_second_geometry(bg1, g2); } break; case Geometry::wkb_polygon: { typename BG_models::Polygon bg1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(), g1->get_srid()); res= distance_dispatch_second_geometry(bg1, g2); } break; case Geometry::wkb_multipolygon: { typename BG_models::Multipolygon bg1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(), g1->get_srid()); res= distance_dispatch_second_geometry(bg1, g2); } break; default: assert(false); break; } } catch (...) { had_except= true; handle_gis_exception("st_distance"); } if (had_except) return error_real(); return res; }