用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5047 lines
161 KiB

3 months ago
/*
Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
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, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "sql/spatial.h"
#include <cmath> // isfinite
#include <map>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>
#include "m_ctype.h"
#include "m_string.h"
#include "my_byteorder.h"
#include "my_dbug.h"
#include "my_macros.h"
#include "my_sys.h"
#include "mysqld_error.h"
#include "prealloced_array.h"
#include "sql/check_stack.h" // check_stack_overrun
#include "sql/current_thd.h"
#include "sql/gis/srid.h"
#include "sql/gis_bg_traits.h" // IWYU pragma: keep
#include "sql/gstream.h" // Gis_read_stream
#include "sql/psi_memory_key.h"
#include "sql/sql_const.h" // STACK_MIN_SIZE
#include "sql_string.h" // String
#include "template_utils.h" // pointer_cast
#include "unsafe_string_append.h"
void *gis_wkb_alloc(size_t sz) {
sz += GEOM_HEADER_SIZE;
char *p = static_cast<char *>(
my_malloc(key_memory_Geometry_objects_data, sz, MYF(MY_FAE)));
p += GEOM_HEADER_SIZE;
return p;
}
void *gis_wkb_realloc(void *p, size_t sz) {
char *cp = static_cast<char *>(p);
if (cp) cp -= GEOM_HEADER_SIZE;
sz += GEOM_HEADER_SIZE;
p = my_realloc(key_memory_Geometry_objects_data, cp, sz, MYF(MY_FAE));
cp = static_cast<char *>(p);
return cp + GEOM_HEADER_SIZE;
}
/***************************** MBR *******************************/
/*
Returns 0/1 if this MBR doesn't/does touch mbr. Returns -1 if the MBRs
contain invalid data. This convention is true for all MBR relation test
functions.
*/
int MBR::touches(const MBR *mbr) const {
const MBR *mbr2 = mbr;
const MBR *mbr1 = this;
int ret = 0;
int dim1 = dimension();
int dim2 = mbr->dimension();
DBUG_ASSERT(dim1 >= 0 && dim1 <= 2 && dim2 >= 0 && dim2 <= 2);
if (dim1 == 0 && dim2 == 0) return 0;
if (dim1 == 0 && dim2 == 1)
return ((mbr1->xmin == mbr2->xmin && mbr1->ymin == mbr2->ymin) ||
(mbr1->xmin == mbr2->xmax && mbr1->ymin == mbr2->ymax));
if (dim1 == 1 && dim2 == 0) return mbr->touches(this);
DBUG_ASSERT(dim1 + dim2 >= 2);
ret = ((mbr2->xmin == mbr1->xmax || mbr2->xmax == mbr1->xmin) &&
(mbr1->ymin <= mbr2->ymax && mbr1->ymax >= mbr2->ymin)) ||
((mbr2->ymin == mbr1->ymax || mbr2->ymax == mbr1->ymin) &&
(mbr1->xmin <= mbr2->xmax && mbr1->xmax >= mbr2->xmin));
if (ret && dim1 == 1 && dim2 == 1) {
// The two line segments may overlap, rather than touch.
int overlaps = ((mbr1->ymin == mbr1->ymax && mbr1->ymin == mbr2->ymax &&
mbr2->ymin == mbr2->ymax && mbr1->xmin < mbr2->xmax &&
mbr1->xmax > mbr2->xmin) ||
(mbr1->xmin == mbr1->xmax && mbr2->xmin == mbr2->xmax &&
mbr1->xmin == mbr2->xmin && mbr1->ymin < mbr2->ymax &&
mbr1->ymax > mbr2->ymin));
if (overlaps) ret = 0;
}
return ret;
}
int MBR::within(const MBR *mbr) const {
int dim1 = dimension();
int dim2 = mbr->dimension();
DBUG_ASSERT(dim1 >= 0 && dim1 <= 2 && dim2 >= 0 && dim2 <= 2);
/*
Either/both of the two operands can degrade to a point or a
horizontal/vertical line segment, and we have to treat such cases
separately.
*/
switch (dim1) {
case 0:
DBUG_ASSERT(xmin == xmax && ymin == ymax);
switch (dim2) {
case 0:
DBUG_ASSERT(mbr->xmin == mbr->xmax && mbr->ymin == mbr->ymax);
return equals(mbr);
break;
case 1:
DBUG_ASSERT((mbr->xmin == mbr->xmax && mbr->ymin != mbr->ymax) ||
(mbr->ymin == mbr->ymax && mbr->xmin != mbr->xmax));
return ((xmin > mbr->xmin && xmin < mbr->xmax && ymin == mbr->ymin) ||
(ymin > mbr->ymin && ymin < mbr->ymax && xmin == mbr->xmin));
break;
case 2:
DBUG_ASSERT(mbr->xmin != mbr->xmax && mbr->ymin != mbr->ymax);
return (xmin > mbr->xmin && xmax < mbr->xmax && ymin > mbr->ymin &&
ymax < mbr->ymax);
break;
}
break;
case 1:
DBUG_ASSERT((xmin == xmax && ymin != ymax) ||
(ymin == ymax && xmin != xmax));
switch (dim2) {
case 0:
DBUG_ASSERT(mbr->xmin == mbr->xmax && mbr->ymin == mbr->ymax);
return 0;
break;
case 1:
DBUG_ASSERT((mbr->xmin == mbr->xmax && mbr->ymin != mbr->ymax) ||
(mbr->ymin == mbr->ymax && mbr->xmin != mbr->xmax));
return (
(xmin == xmax && mbr->xmin == mbr->xmax && mbr->xmin == xmin &&
mbr->ymin <= ymin && mbr->ymax >= ymax) ||
(ymin == ymax && mbr->ymin == mbr->ymax && mbr->ymin == ymin &&
mbr->xmin <= xmin && mbr->xmax >= xmax));
break;
case 2:
DBUG_ASSERT(mbr->xmin != mbr->xmax && mbr->ymin != mbr->ymax);
return ((xmin == xmax && xmin > mbr->xmin && xmax < mbr->xmax &&
ymin >= mbr->ymin && ymax <= mbr->ymax) ||
(ymin == ymax && ymin > mbr->ymin && ymax < mbr->ymax &&
xmin >= mbr->xmin && xmax <= mbr->xmax));
break;
}
break;
case 2:
DBUG_ASSERT(xmin != xmax && ymin != ymax);
switch (dim2) {
case 0:
case 1:
return 0;
break;
case 2:
DBUG_ASSERT(mbr->xmin != mbr->xmax && mbr->ymin != mbr->ymax);
return ((mbr->xmin <= xmin) && (mbr->ymin <= ymin) &&
(mbr->xmax >= xmax) && (mbr->ymax >= ymax));
break;
}
break;
}
// Never reached.
DBUG_ASSERT(false);
return 0;
}
/*
exponential notation :
1 sign
1 number before the decimal point
1 decimal point
17 number of significant digits (see String::qs_append(double))
1 'e' sign
1 exponent sign
3 exponent digits
==
25
"f" notation :
1 optional 0
1 sign
17 number significant digits (see String::qs_append(double) )
1 decimal point
==
20
*/
#define MAX_DIGITS_IN_DOUBLE 25
/**
Distance to another point.
*/
double point_xy::distance(const point_xy &p) const {
/* On 32bit platforms, sqrt(inf) may produce a wrong number that isn't inf. */
const double a = pow(x - p.x, 2.0);
if (!std::isfinite(a)) return a;
const double b = pow(y - p.y, 2.0);
if (!std::isfinite(a + b)) return a + b;
return sqrt(a + b);
}
/***************************** Gis_class_info *******************************/
String Geometry::bad_geometry_data("Bad object", &my_charset_bin);
Geometry::Class_info *Geometry::ci_collection[Geometry::wkb_last + 1] = {NULL};
static Geometry::Class_info **ci_collection_end =
Geometry::ci_collection + Geometry::wkb_last + 1;
Geometry::Class_info::Class_info(const char *name, int type_id,
create_geom_t create_func)
: m_name{name, strlen(name)},
m_type_id(type_id),
m_create_func(create_func) {
ci_collection[type_id] = this;
}
inline static Geometry *create_point(char *buffer) {
return ::new (buffer) Gis_point(false);
}
inline static Geometry *create_linestring(char *buffer) {
return ::new (buffer) Gis_line_string(false);
}
inline static Geometry *create_polygon(char *buffer) {
return ::new (buffer) Gis_polygon(false);
}
inline static Geometry *create_multipoint(char *buffer) {
return ::new (buffer) Gis_multi_point(false);
}
inline static Geometry *create_multipolygon(char *buffer) {
return ::new (buffer) Gis_multi_polygon(false);
}
inline static Geometry *create_multilinestring(char *buffer) {
return ::new (buffer) Gis_multi_line_string(false);
}
inline static Geometry *create_geometrycollection(char *buffer) {
return ::new (buffer) Gis_geometry_collection();
}
/**
Check if geometry type sub is a subtype of super.
Since Geometry::wkbType can't represent the geometry type, the
superclass of all geometry types, this function can't check
that. The supertype has to be a subtype of geometry.
@param sub The type to check
@param super The supertype
@return True if t1 is a subtype of t2
*/
inline static bool is_subtype_of(Geometry::wkbType sub,
Geometry::wkbType super) {
return (super == Geometry::wkb_geometrycollection &&
(sub == Geometry::wkb_multipoint ||
sub == Geometry::wkb_multilinestring ||
sub == Geometry::wkb_multipolygon));
}
static Geometry::Class_info point_class("POINT", Geometry::wkb_point,
create_point);
static Geometry::Class_info linestring_class("LINESTRING",
Geometry::wkb_linestring,
create_linestring);
static Geometry::Class_info polygon_class("POLYGON", Geometry::wkb_polygon,
create_polygon);
static Geometry::Class_info multipoint_class("MULTIPOINT",
Geometry::wkb_multipoint,
create_multipoint);
static Geometry::Class_info multilinestring_class("MULTILINESTRING",
Geometry::wkb_multilinestring,
create_multilinestring);
static Geometry::Class_info multipolygon_class("MULTIPOLYGON",
Geometry::wkb_multipolygon,
create_multipolygon);
static Geometry::Class_info geometrycollection_class(
"GEOMCOLLECTION", Geometry::wkb_geometrycollection,
create_geometrycollection);
/***************************** Geometry *******************************/
Geometry::Class_info *Geometry::find_class(const char *name, size_t len) {
for (Class_info **cur_rt = ci_collection; cur_rt < ci_collection_end;
cur_rt++) {
if (*cur_rt && (*cur_rt)->m_type_id == Geometry::wkb_geometrycollection) {
if (len == 18 &&
my_strnncoll(&my_charset_latin1,
pointer_cast<const uchar *>("GEOMETRYCOLLECTION"), len,
pointer_cast<const uchar *>(name), len) == 0)
return *cur_rt;
} else if (*cur_rt && ((*cur_rt)->m_name.length == len) &&
(my_strnncoll(&my_charset_latin1,
pointer_cast<const uchar *>((*cur_rt)->m_name.str),
len, pointer_cast<const uchar *>(name), len) == 0))
return *cur_rt;
}
return 0;
}
Geometry *Geometry::create_by_typeid(Geometry_buffer *buffer, int type_id) {
Class_info *ci;
if (!(ci = find_class(type_id))) return NULL;
return (*ci->m_create_func)(buffer->data);
}
/**
Construct a Geometry object using GEOMETRY byte string. This function is
called by all GIS functions to make a Geometry object from a GEOMETRY byte
string, which can come from table storage, or returned from other GIS
function, or directly provided by user via client.
The WKB can be of either endianess --- when user directly pass WKB
byte string to us, he/she can pass big endian WKB, otherwise the WKB is
always little endian. And we should reject big endian WKB because all the
rest of the GIS code assumes the internal WKB data being always little endian.
@param buffer The place where the Geometry object is constructed on.
@param data is a byte string with an optional srid prepending a WKB format
byte string, which is called a GEOMETRY byte string and which is the inner
storage format of all geometries in MySQL.
@param data_len number of bytes of the byte string refered by data.
@param has_srid whether data argument starts with an srid or not.
By default it's true, if false, data starts with WKB header, and caller
is responsible to specify an srid to this object later.
@return Constructed geometry object.
*/
Geometry *Geometry::construct(Geometry_buffer *buffer, const char *data,
uint32 data_len, bool has_srid) {
uint32 geom_type;
Geometry *result;
wkbByteOrder bo;
String wkb_le;
uint32 srid_sz = has_srid ? SRID_SIZE : 0;
// Check the GEOMETRY byte string is valid, which would at least have an
// SRID, a WKB header, and 4 more bytes for object count or Point
// coordinate.
if (data_len < srid_sz + WKB_HEADER_SIZE + 4) return NULL;
bo = ::get_byte_order(data + srid_sz);
if (bo != Geometry::wkb_ndr) {
my_error(ER_GIS_DATA_WRONG_ENDIANESS, MYF(0));
return NULL;
/*
Don't try to convert endianess but error out because we can't
replace the bytes refered by data, it can be from any source.
Users can call GeometryFromWKB to use WKB of either endianess
if they have to pass WKB/Geometry byte string from client to us.
*/
}
/* + 1 to skip the byte order (stored in position SRID_SIZE). */
geom_type = uint4korr(data + srid_sz + 1);
if (geom_type < wkb_first || geom_type > wkb_last ||
!(result = create_by_typeid(buffer, (int)geom_type)))
return NULL;
gis::srid_t srid = 0;
if (has_srid) {
srid = uint4korr(data);
result->set_srid(srid);
}
if (geom_type == wkb_point) {
if (data_len - srid_sz - WKB_HEADER_SIZE < POINT_DATA_SIZE) return NULL;
result->set_data_ptr(data + srid_sz + WKB_HEADER_SIZE, POINT_DATA_SIZE);
} else
result->set_data_ptr(data + srid_sz + WKB_HEADER_SIZE,
data_len - srid_sz - WKB_HEADER_SIZE);
result->has_geom_header_space(has_srid);
if (result->get_geotype() == wkb_polygon) result->polygon_is_wkb_form(true);
/*
Check whether the GEOMETRY byte string is a valid and complete one.
Do not allow extra trailing bytes if this is a GEOMETRY byte string,
otherwise we are creating a geometry object using part of the byte string
which is longer than we need here but are not trash bytes.
*/
const uint32 result_len = result->get_data_size();
const uint32 header_size = (has_srid ? GEOM_HEADER_SIZE : WKB_HEADER_SIZE);
if (result_len == GET_SIZE_ERROR ||
(has_srid && (result_len + header_size) != data_len))
return NULL;
return result;
}
/**
Read wkt text from trs, and write little endian wkb encoding into 'wkt',
and create a Geometry instance in 'buffer'. If 'init_stream' is true,
shallow assign data in 'wkt' to the Geometry object to be returned.
@param buffer Place to create the returned Geometry object at.
@param trs WKT read stream.
@param wkb Little endian WKB buffer for WKB data of the returned Geometry
object.
@param init_stream Whether set WKB buffer pointer to returned Geometry
object.
@param check_trailing
@return A Geometry object with data specified by the WKT.
*/
Geometry *Geometry::create_from_wkt(Geometry_buffer *buffer,
Gis_read_stream *trs, String *wkb,
bool init_stream, bool check_trailing) {
LEX_CSTRING name;
Class_info *ci;
if (trs->get_next_word(&name)) {
trs->set_error_msg("Geometry name expected");
return nullptr;
}
if (!(ci = find_class(name.str, name.length)) ||
wkb->reserve(WKB_HEADER_SIZE, 512))
return nullptr;
Geometry *result = (*ci->m_create_func)(buffer->data);
q_append((char)wkb_ndr, wkb);
q_append((uint32)result->get_class_info()->m_type_id, wkb);
if (result->init_from_wkt(trs, wkb) ||
(check_trailing && !trs->is_end_of_stream()))
return nullptr;
if (init_stream)
result->set_data_ptr(wkb->ptr() + WKB_HEADER_SIZE,
wkb->length() - WKB_HEADER_SIZE);
result->has_geom_header_space(true);
if (result->get_geotype() == wkb_polygon) result->polygon_is_wkb_form(true);
return result;
}
/**
Write this geometry's WKB byte string into specified buffer, the SRID is
not written into the buffer.
@param wkb The buffer to write WKB byte string into.
@param shallow_copy Whether do shallow copy by using this object's memory
without owning it or duplicating the byte string.
@return true if got error, false if successful.
*/
bool Geometry::as_wkb(String *wkb, bool shallow_copy) const {
DBUG_ASSERT(wkb->ptr() < get_cptr() - GEOM_HEADER_SIZE ||
wkb->ptr() > get_cptr() + get_nbytes());
if (shallow_copy) {
/*
This object must have GEOMETRY header space, and we simply assign to
wkb, the memory is still owned by the String object of this Geometry
object, i.e. the String object holding WKB data for this object.
Don't write to this object's own String buffer.
*/
DBUG_ASSERT(wkb->ptr() != get_cptr() - GEOM_HEADER_SIZE);
DBUG_ASSERT(!(get_geotype() == wkb_polygon &&
(!polygon_is_wkb_form() || is_bg_adapter())));
wkb->set(get_cptr() - WKB_HEADER_SIZE, get_nbytes() + WKB_HEADER_SIZE,
&my_charset_bin);
return false;
}
if (wkb->reserve(WKB_HEADER_SIZE + this->get_nbytes(), 512) ||
get_data_ptr() == NULL)
return true;
write_wkb_header(wkb, get_geotype());
if (get_geotype() != wkb_polygon)
q_append(static_cast<const char *>(this->get_data_ptr()),
this->get_nbytes(), wkb);
else {
size_t len = 0;
void *ptr = get_packed_ptr(this, &len);
wkb->append(static_cast<char *>(ptr), len);
gis_wkb_free(ptr);
}
return false;
}
/**
Write this geometry's GEOMETRY byte string into specified buffer, the SRID
will be written before the WKB string to form a GEOMETRY byte string.
@param buf The buffer to write GEOMETRY byte string into.
@param shallow_copy Whether do shallow copy by using this object's memory
without owning it or duplicating the byte string.
@return true if got error, false if successful.
*/
bool Geometry::as_geometry(String *buf, bool shallow_copy) const {
if (shallow_copy) {
/*
This object must have GEOMETRY header space, and we simply assign to
buf, the memory is still owned by the String object of this Geometry
object, i.e. the String object holding WKB data for this object.
Don't write to this object's own String buffer.
*/
DBUG_ASSERT(has_geom_header_space());
DBUG_ASSERT(!(get_geotype() == wkb_polygon &&
(!polygon_is_wkb_form() || is_bg_adapter())));
if (buf->ptr() != get_cptr() - GEOM_HEADER_SIZE) {
DBUG_ASSERT(buf->ptr() < get_cptr() - GEOM_HEADER_SIZE ||
buf->ptr() > get_cptr() + get_nbytes());
buf->set(get_cptr() - GEOM_HEADER_SIZE, get_nbytes() + GEOM_HEADER_SIZE,
&my_charset_bin);
}
return false;
}
if ((buf->ptr() == get_cptr() - GEOM_HEADER_SIZE)) {
if (buf->is_alloced()) return false;
} else
DBUG_ASSERT(buf->ptr() < get_cptr() - GEOM_HEADER_SIZE ||
buf->ptr() > get_cptr() + get_nbytes());
if (buf->reserve(SRID_SIZE + WKB_HEADER_SIZE + this->get_nbytes(), 512) ||
get_data_ptr() == NULL)
return true;
write_geometry_header(buf, get_srid(), get_geotype());
if (get_geotype() != wkb_polygon)
q_append(static_cast<const char *>(this->get_data_ptr()),
this->get_nbytes(), buf);
else {
size_t len = 0;
void *ptr = get_packed_ptr(this, &len);
buf->append(static_cast<char *>(ptr), len);
gis_wkb_free(ptr);
}
return false;
}
/**
WKB scanner event handler that checks if the WKB string is well formed.
This doesn't check if the geometry is valid (e.g., it's not checking
if a polygon is self-intersecting), it only checks some simple rules
for WKB well-formedness:
R1. The byte order is as specified (constructor parameter)
R2. The wkbType is within the supported range
R3. The geometry is of the specified type (constructor parameter),
or a subtype
R4. Nested geometries contain only geometries that can be contained
by that type
R5. Linestrings have at least two points
R6. Polygon rings have at least four points
R7. Polygons have at least one ring
R8. Collections, except geometrycollection, contain at least one
element.
An additional requirement, that the WKB ends exactly at the end of
the string, is checked by Geometry::is_well_formed(). The last parse
position is maintained as last_position here to make that test
possible.
*/
class Geometry_well_formed_checker : public WKB_scanner_event_handler {
private:
Prealloced_array<Geometry::wkbType, 8> type; /// Current stack of types
Geometry::wkbType previous_type; /// Type of previous start/end
int points_in_linestring; /// Points in current ls
const void *last_position; /// Last wkb pointer seen
bool is_ok; /// Whether the WKB is OK so far
Geometry::wkbByteOrder required_byte_order;
public:
/**
Create a new even handler.
@param type Expected geometry type. If set to
Geometry::wkb_invalid_type, any geometry is allowed.
@param required_byte_order
*/
Geometry_well_formed_checker(Geometry::wkbType type,
Geometry::wkbByteOrder required_byte_order)
: type(PSI_NOT_INSTRUMENTED),
previous_type(Geometry::wkb_invalid_type),
points_in_linestring(0),
last_position(NULL),
is_ok(true),
required_byte_order(required_byte_order) {
this->type.push_back(type);
}
virtual void on_wkb_start(Geometry::wkbByteOrder bo,
Geometry::wkbType geotype, const void *, uint32,
bool has_hdr) {
if (!is_ok) return;
// The byte order must be the specified one (R1).
if (required_byte_order != Geometry::wkb_invalid &&
bo != required_byte_order) {
is_ok = false;
return;
}
Geometry::wkbType outer_type = type[type.size() - 1];
type.push_back(geotype);
previous_type = geotype;
// The geometry type must be in the valid range (R2).
if (geotype < Geometry::wkb_first ||
geotype > Geometry::wkb_geometrycollection) {
is_ok = false;
return;
}
// First geometry must be of the specified type (if specified), or
// a subtype (R3).
if (type.size() == 2) {
if (geotype != outer_type && outer_type != Geometry::wkb_invalid_type &&
!is_subtype_of(geotype, outer_type))
is_ok = false;
return;
}
// Any type is allowed in geometry collections (R4).
if (outer_type == Geometry::wkb_geometrycollection) return;
switch (geotype) {
case Geometry::wkb_point:
// Points can only appear in multipoints and in linestrings (R4).
if (!(outer_type == Geometry::wkb_multipoint ||
(!has_hdr && outer_type == Geometry::wkb_linestring)))
is_ok = false;
if (outer_type == Geometry::wkb_linestring) ++points_in_linestring;
break;
case Geometry::wkb_linestring:
// Linestrings can only appear in multilinestrings and as rings
// in polygons (R4).
if (!(outer_type == Geometry::wkb_multilinestring ||
(!has_hdr && outer_type == Geometry::wkb_polygon)))
is_ok = false;
break;
case Geometry::wkb_polygon:
// Polygons can only appear in multipolygons (R4).
if (outer_type != Geometry::wkb_multipolygon) is_ok = false;
break;
case Geometry::wkb_multipoint:
case Geometry::wkb_multilinestring:
case Geometry::wkb_multipolygon:
case Geometry::wkb_geometrycollection:
// These are only allowed if outer_type is geometry collection,
// in which case they're handled before entering the switch (R4).
is_ok = false;
break;
default:
// The list of cases above should be complete (R2).
DBUG_ASSERT(0);
break;
}
}
virtual void on_wkb_end(const void *wkb) {
if (!is_ok) return;
Geometry::wkbType current_type = type[type.size() - 1];
type.pop_back();
last_position = wkb;
switch (current_type) {
case Geometry::wkb_linestring:
// Linestrings must have at least two points. Polygon rings must
// have at least four points (R5, R6).
if (points_in_linestring < 2 ||
(type[type.size() - 1] == Geometry::wkb_polygon &&
points_in_linestring < 4))
is_ok = false;
points_in_linestring = 0;
break;
case Geometry::wkb_polygon:
// Polygons must have at least one ring (R7).
if (previous_type != Geometry::wkb_linestring) is_ok = false;
break;
case Geometry::wkb_multipoint:
// Multipoints must contain at least one point (R8).
if (previous_type != Geometry::wkb_point) is_ok = false;
break;
case Geometry::wkb_multilinestring:
// Multilinestrings must contain at least one linestring (R8).
if (previous_type != Geometry::wkb_linestring) is_ok = false;
break;
case Geometry::wkb_multipolygon:
// Multipolygons must contain at least one polygon (R8).
if (previous_type != Geometry::wkb_polygon) is_ok = false;
break;
default:
break;
}
previous_type = current_type;
}
virtual bool continue_scan() const { return is_ok; }
/**
Check if the parsed WKB was well-formed, as far as this handler
knows.
There may be other conditions that cause the object to not be
well-formed.
@see Geometry::is_well_formed
@return True if the WKB was well-formed, false otherwise.
*/
bool is_well_formed() { return is_ok; }
/**
Get the position after the last parsed byte.
@return Pointer pointing to after the last parsed byte.
*/
const char *get_last_position() {
return static_cast<const char *>(last_position);
}
};
bool Geometry::is_well_formed(const char *from, size_t length,
Geometry::wkbType type,
Geometry::wkbByteOrder bo) {
bool is_well_formed = true;
Geometry_well_formed_checker checker(type, bo);
uint32 len = length - SRID_SIZE;
if (length < GEOM_HEADER_SIZE) return false;
is_well_formed = (wkb_scanner(current_thd, from + SRID_SIZE, &len, 0, true,
&checker) != NULL);
return (is_well_formed && checker.is_well_formed() &&
checker.get_last_position() == from + length);
}
static double wkb_get_double(const char *ptr, Geometry::wkbByteOrder bo) {
double res;
if (bo == Geometry::wkb_ndr)
float8get(&res, ptr);
else if (bo == Geometry::wkb_xdr && !is_little_endian())
memcpy(&res, ptr, sizeof(double)); // No need to convert.
else {
/*
Big endian WKB on little endian machine, convert WKB to
standard (little) endianess first.
*/
char inv_array[8];
inv_array[0] = ptr[7];
inv_array[1] = ptr[6];
inv_array[2] = ptr[5];
inv_array[3] = ptr[4];
inv_array[4] = ptr[3];
inv_array[5] = ptr[2];
inv_array[6] = ptr[1];
inv_array[7] = ptr[0];
float8get(&res, inv_array);
}
return res;
}
/**
Check that a pair of geographic coordinates are within the valid range.
Checks if the coordinates are within the allowed range for geographic
coordinates. Valid range for longitude and latitude coordinates in geographic
spatial reference systems are (-180, 180) and [-90, 90] degrees,
respectively.
@param[in] x Longitude coordinate.
@param[in] y Latitude coordinate.
@param[in] srs_angular_unit Unit to radians conversion factor.
@param[out] long_out_of_range Longitude is out of range.
@param[out] lat_out_of_range Latitude is out of range.
@param[out] out_of_range_value The value that is out of range.
@retval false Coordinates are within allowed range.
@retval true Coordinates are not within allowed range.
*/
static bool check_coordinate_range(double x, double y, double srs_angular_unit,
bool *long_out_of_range,
bool *lat_out_of_range,
double *out_of_range_value) {
// Check if longitude coordinates are within allowed range.
if (x * srs_angular_unit <= -M_PI || x * srs_angular_unit > M_PI) {
*long_out_of_range = true;
*out_of_range_value = x;
return true;
}
// Check if latitude coordinates are within allowed range.
if (y * srs_angular_unit < -M_PI_2 || y * srs_angular_unit > M_PI_2) {
*lat_out_of_range = true;
*out_of_range_value = y;
return true;
}
return false;
}
static uint32 wkb_get_uint(const char *ptr, Geometry::wkbByteOrder bo) {
if (bo == Geometry::wkb_ndr)
return uint4korr(ptr);
else if (bo == Geometry::wkb_xdr && !is_little_endian()) {
// Big endian WKB on big endian machine, no need to convert.
uint32 val;
memcpy(&val, ptr, sizeof(uint32));
return val;
} else {
/*
Big endian WKB on little endian machine, convert WKB to
standard (little) endianess first.
*/
char inv_array[4], *inv_array_p = inv_array;
inv_array[0] = ptr[3];
inv_array[1] = ptr[2];
inv_array[2] = ptr[1];
inv_array[3] = ptr[0];
return uint4korr(inv_array_p);
}
}
/**
Scan WKB byte string and notify WKB events by calling registered callbacks.
@param wkb a little endian WKB byte string of 'len' bytes, with or
without WKB header.
@param[in] thd Thread context.
@param [in,out] len remaining number of bytes of the wkb string.
@param geotype the type of the geometry to be scanned.
@param has_hdr whether the 'wkb' point to a WKB header or right after
the header. If it is true, the
'geotype' should be the same as the type in the header;
otherwise, and we will use the type specified in WKB header.
@param handler the registered WKB_scanner_event_handler object to be notified.
@return the next byte after last valid geometry just scanned, or NULL on error
*/
const char *wkb_scanner(THD *thd, const char *wkb, uint32 *len, uint32 geotype,
bool has_hdr, WKB_scanner_event_handler *handler) {
if (check_stack_overrun(current_thd, STACK_MIN_SIZE, nullptr)) return nullptr;
Geometry::wkbType gt;
const char *q = NULL;
uint32 ngeos = 0, comp_type = 0, gtype = 0;
bool comp_hashdr = false, done = false;
if (has_hdr) {
if (*len < WKB_HEADER_SIZE) return NULL; // Invalid WKB data.
gtype = uint4korr(wkb + 1);
// The geotype isn't used in this case.
if (geotype != gtype && geotype != 0 /* unknown */) return NULL;
if ((*wkb != Geometry::wkb_ndr && *wkb != Geometry::wkb_xdr) ||
gtype < Geometry::wkb_first || gtype > Geometry::wkb_last)
return NULL;
gt = static_cast<Geometry::wkbType>(gtype);
q = wkb + WKB_HEADER_SIZE;
*len -= WKB_HEADER_SIZE;
if (*len <= 0) return NULL;
handler->on_wkb_start(get_byte_order(wkb), gt, q, *len, true);
if (!handler->continue_scan()) return NULL;
} else {
DBUG_ASSERT(geotype >= Geometry::wkb_first &&
geotype <= Geometry::wkb_last);
q = wkb;
gt = static_cast<Geometry::wkbType>(geotype);
handler->on_wkb_start(Geometry::wkb_ndr, gt, q, *len, false);
if (!handler->continue_scan()) return NULL;
}
if (gt != Geometry::wkb_point) {
if (*len < 4) return NULL;
ngeos = uint4korr(q);
q += sizeof(uint32);
*len -= 4;
}
switch (gt) {
case Geometry::wkb_point:
if (*len < POINT_DATA_SIZE) return NULL;
q += POINT_DATA_SIZE;
*len -= POINT_DATA_SIZE;
done = true;
handler->on_wkb_end(q);
if (!handler->continue_scan()) return NULL;
break;
case Geometry::wkb_linestring:
comp_type = Geometry::wkb_point;
break;
case Geometry::wkb_polygon:
comp_type = Geometry::wkb_linestring;
break;
case Geometry::wkb_multipoint:
comp_type = Geometry::wkb_point;
comp_hashdr = true;
break;
case Geometry::wkb_multilinestring:
comp_type = Geometry::wkb_linestring;
comp_hashdr = true;
break;
case Geometry::wkb_multipolygon:
comp_type = Geometry::wkb_polygon;
comp_hashdr = true;
break;
case Geometry::wkb_geometrycollection:
comp_hashdr = true;
break;
default:
DBUG_ASSERT(false);
break;
}
if (!done && q != NULL) {
for (uint32 i = 0; i < ngeos; i++) {
q = wkb_scanner(thd, q, len, comp_type, comp_hashdr, handler);
if (q == NULL) return NULL;
}
handler->on_wkb_end(q);
if (!handler->continue_scan()) return NULL;
}
return q;
}
/**
Read from 'wkb' (which contains WKB encoded in either endianess) the
geometry data, and write WKB of returned Geometry object in little endianess
into 'res', and also create geometry object on 'buffer' and return it.
The returned Geometry object points to bytes (without WKB HEADER) in 'res'.
@param thd Thread context.
@param buffer the place to create the returned Geometry object at.
@param wkb the input WKB buffer which contains WKB of either endianess.
@param len the number of bytes of WKB in 'wkb'.
@param res the buffer to write little endian WKB into.
@param init_stream Whether set WKB buffer pointer to returned Geometry
object.
@return the created Geometry object.
*/
Geometry *Geometry::create_from_wkb(THD *thd, Geometry_buffer *buffer,
const char *wkb, uint32 len, String *res,
bool init_stream) {
uint32 geom_type;
Geometry *geom;
if (len < WKB_HEADER_SIZE) return NULL;
geom_type = wkb_get_uint(wkb + 1, ::get_byte_order(wkb));
if ((*wkb != wkb_xdr && *wkb != wkb_ndr) || geom_type < wkb_first ||
geom_type > wkb_last ||
!(geom = create_by_typeid(buffer, (int)geom_type)) ||
res->reserve(WKB_HEADER_SIZE, 512))
return NULL;
q_append((char)wkb_ndr, res);
q_append(geom_type, res);
uint tret =
geom->init_from_wkb(thd, wkb + WKB_HEADER_SIZE, len - WKB_HEADER_SIZE,
::get_byte_order(wkb), res);
// The WKB string is invalid if it has trailing trash bytes.
if (tret != len - WKB_HEADER_SIZE) return NULL;
if (init_stream)
geom->set_data_ptr(res->ptr() + WKB_HEADER_SIZE,
res->length() - WKB_HEADER_SIZE);
geom->has_geom_header_space(true);
if (geom->get_geotype() == wkb_polygon) geom->polygon_is_wkb_form(true);
return tret ? geom : NULL;
}
bool Geometry::envelope(MBR *mbr) const {
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (get_mbr(mbr, &wkb)) return true;
return false;
}
class GeomColl_component_counter : public WKB_scanner_event_handler {
public:
size_t num;
GeomColl_component_counter() : num(0) {}
virtual void on_wkb_start(Geometry::wkbByteOrder, Geometry::wkbType geotype,
const void *, uint32, bool) {
if (geotype != Geometry::wkb_geometrycollection) num++;
}
virtual void on_wkb_end(const void *) {}
};
bool Geometry::envelope(String *result) const {
MBR mbr;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (result->reserve(1 + 4 * 3 + SIZEOF_STORED_DOUBLE * 10)) return true;
if (get_mbr(&mbr, &wkb)) {
/*
The geometry has no effective components in this branch, which is
impossible for geometries other than geometry collections(GC).
A GC may have empty nested GCs.
*/
if (get_type() != wkb_geometrycollection) return true;
uint32 num = uint4korr(get_cptr());
if (num != 0) {
GeomColl_component_counter counter;
uint32 wkb_len = get_data_size();
wkb_scanner(current_thd, get_cptr(), &wkb_len,
Geometry::wkb_geometrycollection, false, &counter);
// Non-empty nested geometry collections.
if (counter.num > 0) return true;
}
// An empty geometry collection's envelope is an empty geometry.
write_wkb_header(result, wkb_geometrycollection, 0);
return false;
}
q_append(static_cast<char>(wkb_ndr), result);
int dim = mbr.dimension();
if (dim < 0) return true;
uint32 num_elems, num_elems2;
if (dim == 0) {
q_append(static_cast<uint32>(wkb_point), result);
q_append(mbr.xmin, result);
q_append(mbr.ymin, result);
} else if (dim == 1) {
q_append(static_cast<uint32>(wkb_linestring), result);
num_elems = 2;
q_append(num_elems, result);
q_append(mbr.xmin, result);
q_append(mbr.ymin, result);
q_append(mbr.xmax, result);
q_append(mbr.ymax, result);
} else {
q_append(static_cast<uint32>(wkb_polygon), result);
num_elems = 1;
q_append(num_elems, result);
num_elems2 = 5;
q_append(num_elems2, result);
q_append(mbr.xmin, result);
q_append(mbr.ymin, result);
q_append(mbr.xmax, result);
q_append(mbr.ymin, result);
q_append(mbr.xmax, result);
q_append(mbr.ymax, result);
q_append(mbr.xmin, result);
q_append(mbr.ymax, result);
q_append(mbr.xmin, result);
q_append(mbr.ymin, result);
}
return false;
}
/**
Create a point from data.
@param [out] result Put result here
@param wkb Data for point is here.
@return false on success, true on error
*/
bool Geometry::create_point(String *result, wkb_parser *wkb) const {
if (wkb->no_data(POINT_DATA_SIZE) ||
result->reserve(WKB_HEADER_SIZE + POINT_DATA_SIZE, 32))
return true;
q_append((char)wkb_ndr, result);
q_append((uint32)wkb_point, result);
/* Copy two double in same format */
q_append(wkb->data(), POINT_DATA_SIZE, result);
return false;
}
/**
Create a point from coordinates.
@param [out] result
@param p coordinates for point
@return false on success, true on error
*/
bool Geometry::create_point(String *result, point_xy p) const {
if (result->reserve(1 + 4 + POINT_DATA_SIZE, 32)) return true;
q_append((char)wkb_ndr, result);
q_append((uint32)wkb_point, result);
q_append(p.x, result);
q_append(p.y, result);
return false;
}
/**
Append N points from packed format to text
Before calling this function, caller must have already checked that wkb's
buffer is complete and not truncated.
@param [out] txt Append points here
@param n_points Number of points
@param wkb Packed data
@param offset Offset between points
@param bracket_pt whether to bracket the point coordinate with (),
multipoint need so.
*/
void Geometry::append_points(String *txt, uint32 n_points, wkb_parser *wkb,
uint32 offset, bool bracket_pt) const {
DBUG_ASSERT(0.0 == 0 && 0 == -0 && -0.0 == 0.0);
while (n_points--) {
point_xy p;
wkb->skip_unsafe(offset);
wkb->scan_xy_unsafe(&p);
txt->reserve(MAX_DIGITS_IN_DOUBLE * 2 + 1);
if (bracket_pt) qs_append('(', txt);
qs_append(p.x, MAX_DIGITS_IN_DOUBLE, txt);
qs_append(' ', txt);
qs_append(p.y, MAX_DIGITS_IN_DOUBLE, txt);
if (bracket_pt) qs_append(')', txt);
qs_append(',', txt);
}
}
/**
Get most bounding rectangle (mbr) for X points
@param [out] mbr Result
@param wkb Data for point is here.
@param offset Offset between points
@return false on success, true on error
*/
bool Geometry::get_mbr_for_points(MBR *mbr, wkb_parser *wkb,
uint offset) const {
uint32 n_points;
if (wkb->scan_n_points_and_check_data(&n_points, offset)) return true;
/* Calculate MBR for points */
while (n_points--) {
wkb->skip_unsafe(offset);
point_xy p;
wkb->scan_xy_unsafe(&p);
if (!std::isfinite(p.x) || !std::isfinite(p.y)) return true;
mbr->add_xy(p);
}
return false;
}
Geometry::Geometry(const Geometry &geo) {
#if !defined(DBUG_OFF)
wkbType geotype = geo.get_geotype();
#endif
DBUG_ASSERT(is_valid_geotype(geotype) &&
((geo.get_ptr() != NULL && geo.get_nbytes() > 0) ||
(geo.get_ptr() == NULL && geo.get_nbytes() == 0) ||
(geo.get_geotype() == wkb_polygon && geo.get_nbytes() == 0)));
m_ptr = geo.m_ptr;
m_flags = geo.m_flags;
m_owner = geo.m_owner;
set_srid(geo.get_srid());
}
Geometry::~Geometry() {
/*
Make sure no exceptions can be thrown in destructors of Geometry classes,
by asserting in debug builds, so that future code won't accidentally throw.
If an exception is thrown when we destroy a geometry object G,
although the exception will still be caught and converted to MySQL
error report, the geometries that are in the same container as G and
that are placed after G will not be properly destroyed. This is the
problem we want to address/avoid by forbiding throwing exceptions in
destructors of Geometry classes.
Since DBUG_ASSERT only works when DBUG_OFF is not defined, the
try/catch is only enabled here depending on the same condition, so that
in release builds we don't have the overhead of the try-catch statement.
This is true also for destructors of children classes of Geometry.
*/
#if !defined(DBUG_OFF)
try {
#endif
if (!is_bg_adapter()) return;
Geometry::wkbType gt = get_geotype();
if (gt != Geometry::wkb_polygon) {
if (get_ownmem() && m_ptr) {
set_ownmem(false);
gis_wkb_free(m_ptr);
m_ptr = NULL;
}
set_nbytes(0);
}
donate_data();
#if !defined(DBUG_OFF)
} catch (...) {
// Should never throw exceptions in destructor.
DBUG_ASSERT(false);
}
#endif
}
/**
Assignment operator for Geometry class, assignment operators of children
classes calls this to do general assignment.
*/
Geometry &Geometry::operator=(const Geometry &rhs) {
if (this == &rhs) return *this;
#if !defined(DBUG_OFF)
Geometry::wkbType geotype = rhs.get_geotype();
#endif
DBUG_ASSERT((is_bg_adapter() || rhs.is_bg_adapter()) &&
m_flags.geotype == rhs.m_flags.geotype &&
is_valid_geotype(geotype));
set_bg_adapter(true);
/*
Update the mutable state of rhs.
*/
rhs.set_bg_adapter(true);
set_srid(rhs.get_srid());
// Don't set_flags, it's done in operator= of children classes.
return *this;
}
/***************************** Point *******************************/
Gis_point::Gis_point(const self &pt) : Geometry(pt) {
size_t nbytes = get_nbytes();
DBUG_ASSERT((nbytes == SIZEOF_STORED_DOUBLE * GEOM_DIM || nbytes == 0));
if (nbytes == 0) {
DBUG_ASSERT(get_ownmem() == false);
// Allocate even if pt isn't initialized with proper value, this is
// required behavior from Boost Geometry.
nbytes = SIZEOF_STORED_DOUBLE * GEOM_DIM;
set_nbytes(nbytes);
}
m_ptr = gis_wkb_fixed_alloc(nbytes);
if (m_ptr == NULL) {
set_nbytes(0);
set_ownmem(0);
return;
}
if (pt.get_nbytes() > 0)
memcpy(m_ptr, pt.get_ptr(), pt.get_nbytes());
else
memset(m_ptr, 0, nbytes);
set_ownmem(true);
}
/**
Deep assignment from point 'p' to this object.
@param rhs the Gis_point to duplicate from.
*/
Gis_point &Gis_point::operator=(const Gis_point &rhs) {
if (this == &rhs) return *this;
Geometry::operator=(rhs);
// This point may or may not have own memory. we allow this because in bg,
// std::reverse is called to reverse a linestring/ring, and also,
// points are of equal size. Not allowed on any other type of geometries.
DBUG_ASSERT(
(m_ptr != NULL && get_nbytes() == SIZEOF_STORED_DOUBLE * GEOM_DIM) ||
(m_ptr == NULL && get_nbytes() == 0 && !get_ownmem()));
DBUG_ASSERT(
(rhs.get_ptr() != NULL &&
rhs.get_nbytes() == SIZEOF_STORED_DOUBLE * GEOM_DIM) ||
(rhs.get_ptr() == NULL && rhs.get_nbytes() == 0 && !rhs.get_ownmem()));
if (m_owner == NULL) m_owner = rhs.get_owner();
size_t plen = rhs.get_nbytes();
// May or may not have own memory. We allow assignment to a Gis_point
// owning no memory because in BG, std::reverse is called to reverse a
// linestring/ring, and also, points are of equal size.
// Not allowed on any other type of geometries.
if (m_ptr == NULL) {
set_nbytes(SIZEOF_STORED_DOUBLE * GEOM_DIM);
set_ownmem(true);
m_ptr = gis_wkb_fixed_alloc(get_nbytes());
if (m_ptr == NULL) {
set_nbytes(0);
set_ownmem(0);
return *this;
}
}
/*
Boost Geometry may use a point that is only default constructed that has
no meaningful value, and in such a case the default value are all zeros.
*/
if (plen > 0)
memcpy(m_ptr, rhs.get_ptr(), plen);
else
memset(m_ptr, 0, get_nbytes());
return *this;
}
/**
Shallow assignment, let this point object refer to the specified memory
address as its WKB data, and this object never owns the memory assigned.
@param ptr WKB data address for the point.
@param len WKB data length for the point.
*/
void Gis_point::set_ptr(void *ptr, size_t len) {
set_bg_adapter(true);
if (m_ptr && get_ownmem()) {
DBUG_ASSERT(get_nbytes() == SIZEOF_STORED_DOUBLE * GEOM_DIM);
gis_wkb_free(m_ptr);
}
m_ptr = ptr;
set_nbytes(len);
set_ownmem(false);
DBUG_ASSERT(
(m_ptr != NULL && get_nbytes() == SIZEOF_STORED_DOUBLE * GEOM_DIM) ||
(m_ptr == NULL && get_nbytes() == 0));
}
uint32 Gis_point::get_data_size() const {
if (check_stack_overrun(current_thd, STACK_MIN_SIZE, nullptr))
return GET_SIZE_ERROR;
if (get_nbytes() != POINT_DATA_SIZE) return GET_SIZE_ERROR;
return POINT_DATA_SIZE;
}
bool Gis_point::init_from_wkt(Gis_read_stream *trs, String *wkb,
const bool parens) {
if (check_stack_overrun(trs->thd(), STACK_MIN_SIZE, nullptr)) return true;
double x, y;
if ((parens && trs->check_next_symbol('(')) || trs->get_next_number(&x) ||
trs->get_next_number(&y) || wkb->reserve(POINT_DATA_SIZE, 256) ||
(parens && trs->check_next_symbol(')')))
return true;
q_append(x, wkb);
q_append(y, wkb);
return false;
}
/*
Initialize point using WKB data, the WKB data may come from user input or
internally table-stored geometry, and user's WKB data may be either endian.
The WKB data may be of either little endian or big endian, thus we need to
read them using endian-ness aware functions wkb_get_uint, wkb_get_double,
and store them as portable (little endian) format into res.
Only xxx_from_wkb SQL functions can see big endian WKB data, all other
function Items see portable little endian WKB data.
This is true for all the init_from_wkb functions of all Geometry classes.
*/
uint Gis_point::init_from_wkb(THD *thd, const char *wkb, uint len,
wkbByteOrder bo, String *res) {
if (check_stack_overrun(thd, STACK_MIN_SIZE, nullptr)) return 0;
double x, y;
if (len < POINT_DATA_SIZE || res->reserve(POINT_DATA_SIZE, 256)) return 0;
x = wkb_get_double(wkb, bo);
y = wkb_get_double(wkb + SIZEOF_STORED_DOUBLE, bo);
q_append(x, res);
q_append(y, res);
return POINT_DATA_SIZE;
}
bool Gis_point::get_data_as_wkt(String *txt, wkb_parser *wkb) const {
point_xy p;
if (wkb->scan_xy(&p)) return true;
if (txt->reserve(MAX_DIGITS_IN_DOUBLE * 2 + 3)) return true;
if (!std::isfinite(p.x) || !std::isfinite(p.y)) return true;
qs_append('(', txt);
qs_append(p.x, MAX_DIGITS_IN_DOUBLE, txt);
qs_append(' ', txt);
qs_append(p.y, MAX_DIGITS_IN_DOUBLE, txt);
qs_append(')', txt);
return false;
}
bool Gis_point::get_mbr(MBR *mbr, wkb_parser *wkb) const {
point_xy p;
if (wkb->scan_xy(&p)) return true;
if (!std::isfinite(p.x) || !std::isfinite(p.y)) return true;
mbr->add_xy(p);
return false;
}
bool Gis_point::reverse_coordinates() {
double x;
double y;
if (get_x(&x) || get_y(&y)) {
return true;
}
float8store(get_cptr(), y);
float8store((get_cptr() + SIZEOF_STORED_DOUBLE), x);
return false;
}
bool Gis_point::validate_coordinate_range(double srs_angular_unit,
bool *long_out_of_range,
bool *lat_out_of_range,
double *out_of_range_value) {
double x;
double y;
*long_out_of_range = false;
*lat_out_of_range = false;
if (get_x(&x) || get_y(&y)) {
return true; /* purecov: inspected */
}
return check_coordinate_range(x, y, srs_angular_unit, long_out_of_range,
lat_out_of_range, out_of_range_value);
}
const Geometry::Class_info *Gis_point::get_class_info() const {
return &point_class;
}
/***************************** LineString *******************************/
uint32 Gis_line_string::get_data_size() const {
if (check_stack_overrun(current_thd, STACK_MIN_SIZE, nullptr))
return GET_SIZE_ERROR;
if (is_length_verified()) return static_cast<uint32>(get_nbytes());
uint32 n_points;
uint32 len;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_n_points_and_check_data(&n_points)) return GET_SIZE_ERROR;
len = 4 + n_points * POINT_DATA_SIZE;
if (len != get_nbytes()) set_nbytes(len);
set_length_verified(true);
return len;
}
// Helper function to get coordinate value from a linestring of WKB format.
inline double coord_val(const char *p, int i, int x) {
double val = 0;
float8get(&val, p + i * POINT_DATA_SIZE + (x ? SIZEOF_STORED_DOUBLE : 0));
return val;
}
bool Gis_line_string::init_from_wkt(Gis_read_stream *trs, String *wkb) {
if (check_stack_overrun(trs->thd(), STACK_MIN_SIZE, nullptr)) return true;
uint32 n_points = 0;
uint32 np_pos = wkb->length();
Gis_point p(false);
if (trs->check_next_symbol('(')) return true;
if (wkb->reserve(4, 512)) return true;
wkb->length(wkb->length() + 4); // Reserve space for points
for (;;) {
if (p.init_from_wkt(trs, wkb, false)) return true;
n_points++;
if (trs->skip_char(',')) // Didn't find ','
break;
}
if (n_points < 2) {
trs->set_error_msg("Too few points in LINESTRING");
return true;
}
const char *firstpt = NULL, *lastpt = NULL;
if (!is_polygon_ring()) goto out;
// Make sure all rings of a polygon are closed, and a ring must have
// at least 4 points.
firstpt = wkb->ptr() + np_pos + 4;
lastpt = wkb->ptr() + wkb->length() - POINT_DATA_SIZE;
if (n_points < 4 || memcmp(lastpt, firstpt, POINT_DATA_SIZE)) return true;
DBUG_ASSERT(n_points == (lastpt - firstpt) / POINT_DATA_SIZE + 1);
out:
write_at_position(np_pos, n_points, wkb);
if (trs->check_next_symbol(')')) return true;
return false;
}
uint Gis_line_string::init_from_wkb(THD *thd, const char *wkb, uint len,
wkbByteOrder bo, String *res) {
if (check_stack_overrun(thd, STACK_MIN_SIZE, nullptr)) return 0;
uint32 n_points, proper_length;
const char *wkb_end;
Gis_point p(false);
if (len < 4 || (n_points = wkb_get_uint(wkb, bo)) < 2 ||
(is_polygon_ring() && n_points < 4) || n_points > max_n_points)
return 0;
proper_length = 4 + n_points * POINT_DATA_SIZE;
wkb_end = wkb + proper_length;
if (len < proper_length) return 0;
// Make sure all rings of a polygon are closed.
if (is_polygon_ring() &&
memcmp(wkb + 4, wkb_end - POINT_DATA_SIZE, POINT_DATA_SIZE))
return 0;
if (res->reserve(proper_length, 512)) return 0;
q_append(n_points, res);
for (wkb += 4; wkb < wkb_end; wkb += POINT_DATA_SIZE) {
if (!p.init_from_wkb(thd, wkb, POINT_DATA_SIZE, bo, res)) return 0;
}
return proper_length;
}
bool Gis_line_string::get_data_as_wkt(String *txt, wkb_parser *wkb) const {
uint32 n_points;
if (wkb->scan_n_points_and_check_data(&n_points) ||
txt->reserve(1 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points))
return true;
qs_append('(', txt);
while (n_points--) {
point_xy p;
wkb->scan_xy_unsafe(&p);
if (!std::isfinite(p.x) || !std::isfinite(p.y)) return true;
qs_append(p.x, MAX_DIGITS_IN_DOUBLE, txt);
qs_append(' ', txt);
qs_append(p.y, MAX_DIGITS_IN_DOUBLE, txt);
qs_append(',', txt);
}
txt->length(txt->length() - 1); // Remove end ','
qs_append(')', txt);
return false;
}
bool Gis_line_string::get_mbr(MBR *mbr, wkb_parser *wkb) const {
return get_mbr_for_points(mbr, wkb, 0);
}
int Gis_line_string::geom_length(double *len) const {
uint32 n_points;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
*len = 0; // In case of errors
if (wkb.scan_n_points_and_check_data(&n_points)) return 1;
point_xy prev;
wkb.scan_xy_unsafe(&prev);
while (--n_points) {
point_xy p;
wkb.scan_xy_unsafe(&p);
*len += prev.distance(p);
if (!std::isfinite(*len)) return 1;
prev = p;
}
return 0;
}
int Gis_line_string::is_closed(int *closed) const {
uint32 n_points;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_n_points_and_check_data(&n_points)) return 1;
if (n_points == 1) {
*closed = 1;
return 0;
}
point_xy p1, p2;
/* Get first point. */
wkb.scan_xy_unsafe(&p1);
/* Get last point. */
wkb.skip_unsafe((n_points - 2) * POINT_DATA_SIZE);
wkb.scan_xy_unsafe(&p2);
*closed = p1.eq(p2);
return 0;
}
int Gis_line_string::num_points(uint32 *n_points) const {
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
return wkb.scan_uint4(n_points) ? 1 : 0;
}
int Gis_line_string::start_point(String *result) const {
uint32 n_points;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_n_points_and_check_data(&n_points)) return 1;
return create_point(result, &wkb);
}
int Gis_line_string::end_point(String *result) const {
uint32 n_points;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_n_points_and_check_data(&n_points)) return 1;
wkb.skip_unsafe((n_points - 1) * POINT_DATA_SIZE);
return create_point(result, &wkb);
}
int Gis_line_string::point_n(uint32 num, String *result) const {
uint32 n_points;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (num < 1 || wkb.scan_n_points_and_check_data(&n_points) || num > n_points)
return 1;
wkb.skip_unsafe((num - 1) * POINT_DATA_SIZE);
return create_point(result, &wkb);
}
bool Gis_line_string::reverse_coordinates() {
uint32 num_of_points;
if (num_points(&num_of_points)) {
return true;
}
for (uint32 i = 0; i < num_of_points; i++) {
double x;
double y;
// +4 in below functions to skip numPoints field.
float8get(&x, get_cptr() + 4 + i * POINT_DATA_SIZE);
float8get(&y, get_cptr() + 4 + i * POINT_DATA_SIZE + SIZEOF_STORED_DOUBLE);
float8store(get_cptr() + 4 + i * POINT_DATA_SIZE, y);
float8store(get_cptr() + 4 + i * POINT_DATA_SIZE + SIZEOF_STORED_DOUBLE, x);
}
return false;
}
bool Gis_line_string::validate_coordinate_range(double srs_angular_unit,
bool *long_out_of_range,
bool *lat_out_of_range,
double *out_of_range_value) {
uint32 num_of_points;
*long_out_of_range = false;
*lat_out_of_range = false;
if (num_points(&num_of_points)) {
return true; /* purecov: inspected */
}
for (uint32 i = 0; i < num_of_points; i++) {
double x;
double y;
// +4 in below functions to skip numPoints field.
float8get(&x, get_cptr() + 4 + i * POINT_DATA_SIZE);
float8get(&y, get_cptr() + 4 + i * POINT_DATA_SIZE + SIZEOF_STORED_DOUBLE);
if (check_coordinate_range(x, y, srs_angular_unit, long_out_of_range,
lat_out_of_range, out_of_range_value)) {
return true;
}
}
return false;
}
const Geometry::Class_info *Gis_line_string::get_class_info() const {
return &linestring_class;
}
/***************************** Polygon *******************************/
/**
Copy constructor.
Coordinate type, closed-ness and direction will never change.
@param r another polygon of same coordinate type, ring closed-ness and
ring direction.
*/
Gis_polygon::Gis_polygon(const self &r) : Geometry(r), m_inn_rings(NULL) {
Gis_polygon::ring_type *r_out = NULL, *outer = NULL;
Gis_polygon::inner_container_type *r_inners = NULL, *inners = NULL;
if (r.is_bg_adapter() == false || r.get_ptr() == NULL) return;
std::unique_ptr<Gis_polygon::ring_type> guard1;
std::unique_ptr<Gis_polygon::inner_container_type> guard2;
if (r.get_ptr()) {
r_out = outer_ring(&r);
outer = new Gis_polygon::ring_type(*r_out);
guard1.reset(outer);
outer->set_owner(this);
m_ptr = outer;
}
if (r.inner_rings()) {
r_inners = r.inner_rings();
inners = new Gis_polygon::inner_container_type(*r_inners);
guard2.reset(inners);
inners->set_owner(this);
set_inner_rings(inners);
}
set_ownmem(false);
set_bg_adapter(true);
guard1.release();
guard2.release();
}
Gis_polygon::Gis_polygon(const void *wkb, size_t nbytes, const Flags_t &flags,
gis::srid_t srid)
: Geometry(NULL, nbytes, flags, srid) {
set_geotype(wkb_polygon);
// Make use of Gis_wkb_vector::parse_wkb_data.
inner_container_type v(wkb, nbytes, get_flags(), srid);
m_ptr = v.get_ptr();
m_inn_rings = reinterpret_cast<inner_container_type *>(v.get_geo_vect());
set_ownmem(false);
if (m_ptr) outer_ring(this)->set_owner(this);
if (m_inn_rings) m_inn_rings->set_owner(this);
set_bg_adapter(true);
v.donate_data();
}
Gis_polygon::~Gis_polygon() {
/* See ~Geometry() for why we do try-catch like this. */
#if !defined(DBUG_OFF)
try {
#endif
if (!is_bg_adapter() && !get_ownmem()) return;
if (m_ptr) {
if (polygon_is_wkb_form()) {
if (get_ownmem()) gis_wkb_free(m_ptr);
} else
delete outer_ring(this);
m_ptr = NULL;
}
if (m_inn_rings) {
delete m_inn_rings;
m_inn_rings = NULL;
}
/*
Never need to free polygon's wkb memory here, because if it's one chunk
given to us, we don't own it; otherwise the two pieces are already freed
above.
*/
#if !defined(DBUG_OFF)
} catch (...) {
// Should never throw exceptions in destructor.
DBUG_ASSERT(false);
}
#endif
}
/**
Deep assignment from polygon 'o' to this object.
@param rhs the Gis_polygon instance to duplicate from.
*/
Gis_polygon &Gis_polygon::operator=(const Gis_polygon &rhs) {
if (this == &rhs || !is_bg_adapter() || !rhs.is_bg_adapter()) return *this;
Geometry::operator=(rhs);
this->set_flags(rhs.get_flags());
if (this->m_owner == 0) this->m_owner = rhs.get_owner();
if (m_ptr) delete outer_ring(this);
if (m_inn_rings) delete m_inn_rings;
m_ptr = NULL;
m_inn_rings = NULL;
if (rhs.get_ptr()) {
Gis_polygon::ring_type *outer =
new Gis_polygon::ring_type(*outer_ring(&rhs));
outer->set_owner(this);
m_ptr = outer;
}
if (rhs.inner_rings()) {
Gis_polygon::inner_container_type *inners =
new Gis_polygon::inner_container_type(*rhs.inner_rings());
inners->set_owner(this);
set_inner_rings(inners);
}
return *this;
}
/**
Set WKB data to this object, the WKB data will be used read only.
@param ptr WKB data pointer.
@param len WKB data number of bytes.
*/
void Gis_polygon::set_ptr(void *ptr, size_t len) {
set_bg_adapter(true);
ring_type *outer = outer_ring(this);
if (outer) delete outer;
if (m_inn_rings) delete m_inn_rings;
set_nbytes(len);
Gis_wkb_vector<ring_type> plgn(ptr, len, get_flags(), get_srid());
m_ptr = plgn.get_ptr();
m_inn_rings = reinterpret_cast<inner_container_type *>(plgn.get_geo_vect());
outer = outer_ring(this);
if (outer != NULL) outer->set_owner(this);
if (m_inn_rings != NULL) m_inn_rings->set_owner(this);
// Prevent destructor deleting m_ptr/m_inn_rings.
plgn.donate_data();
}
/**
Make the polygon's data in a single buffer as WKB format. This polygon
must be one for BG use before this call, and after this call it can
never be passed into BG functions directly after this call, and it
is suitable as a Gis_polygon for MySQL GIS code, because it's exactly the
same as a Gis_polygon object returned by Geometry::create_from_wkt/wkb.
*/
void Gis_polygon::to_wkb_unparsed() {
DBUG_ASSERT(polygon_is_wkb_form() == false && is_bg_adapter());
size_t nbytes = 0;
void *ptr = get_packed_ptr(this, &nbytes);
delete outer_ring(this);
delete m_inn_rings;
m_ptr = ptr;
set_nbytes(nbytes);
m_inn_rings = NULL;
polygon_is_wkb_form(true);
set_bg_adapter(false);
set_ownmem(true);
}
/**
Set the specified ring to counter-clockwise(CCW) or clockwise(CW) if it's not.
Assuems the ring is closed, i.e. the 1st point is the same as the last one.
Allow duplicate vertices at any position, even rings degraded to a point;
Works for convex and concave rings; Can detect those with spikes and
reject them.
@param want_ccw whether want CCW ring(true) or CW ring(false).
@return false if successful, true if got error -- invalid geometry data.
*/
bool Gis_polygon_ring::set_ring_order(bool want_ccw) {
DBUG_ASSERT(is_bg_adapter());
Gis_polygon_ring &ring = *this;
double x1, x2, y1, y2, minx = DBL_MAX, miny = DBL_MAX;
size_t min_i = 0, prev_i, post_i, rsz = ring.size();
static_assert(sizeof(double) == POINT_DATA_SIZE / 2 &&
sizeof(double) == SIZEOF_STORED_DOUBLE,
"");
/*
User input WKT/WKB may contain invalid geometry data that has less
than 3 points in a polygon ring, so we should return error in this case.
*/
if (rsz < 3) return true;
for (size_t i = 0; i < rsz; i++) {
x1 = ring[i].get<0>();
y1 = ring[i].get<1>();
if (i == 0) {
minx = x1;
miny = y1;
continue;
}
if (x1 < minx) {
minx = x1;
miny = y1;
min_i = i;
} else if (x1 == minx) {
if (y1 < miny) {
miny = y1;
min_i = i;
}
}
}
prev_i = min_i - 1;
post_i = min_i + 1;
if (min_i == 0) {
// 1st pt and last pt is the same pt, i.e. a closed polygon data, we
// shouldn't use the last pt as prev_i in this case otherwise we will
// get 0 sign.
if (ring[0].get<0>() == ring[rsz - 1].get<0>() &&
ring[0].get<1>() == ring[rsz - 1].get<1>()) {
prev_i = rsz - 2;
/*
Survive from continuous duplicates before prev_i points.
*/
while (ring[prev_i].get<0>() == ring[min_i].get<0>() &&
ring[prev_i].get<1>() == ring[min_i].get<1>()) {
prev_i--;
/*
Since the ring must be closed, we will never arrive at the first point
otherwise the 1st point would be the min_i and all points would be
the same and this ring would be a point.
*/
if (prev_i == static_cast<size_t>(-1)) return true;
}
} else
prev_i = rsz - 1;
} else if (min_i == rsz - 1) {
// Since we are scanning from 1st point, it's impossible for post_i(0) to
// be the same as min_i here.
post_i = 0;
// Can never come here if all polygon rings are closed.
return true;
}
/*
Survive from continuous duplicates after min_i points. min_i must be
the 1st of such duplicates if any, so no duplicates before min_i.
*/
while (ring[post_i].get<0>() == ring[min_i].get<0>() &&
ring[post_i].get<1>() == ring[min_i].get<1>()) {
post_i++;
/*
Since the ring must be closed, we will never arrive at the last point
otherwise the 1st point would be the min_i, i.e. all points are the same,
and this ring would be a point.
*/
if (post_i == rsz) return true;
}
// The triangle's area tells the direction.
x1 = ring[min_i].get<0>() - ring[prev_i].get<0>();
y1 = ring[min_i].get<1>() - ring[prev_i].get<1>();
x2 = ring[post_i].get<0>() - ring[min_i].get<0>();
y2 = ring[post_i].get<1>() - ring[min_i].get<1>();
double sign = x1 * y2 - x2 * y1;
if (sign == 0) return true; // Catches error: there is a spike in the ring.
// Reverse points in the ring, do direct memory manipulation rather
// than using std::reverse for better performance.
if ((sign > 0 && !want_ccw) || (sign < 0 && want_ccw)) {
char *p = static_cast<char *>(ring.get_ptr()) + sizeof(uint32);
char *q = NULL, *p0;
char pt[POINT_DATA_SIZE];
size_t s = ring.size();
DBUG_ASSERT(ring.get_nbytes() == (s * POINT_DATA_SIZE + 4));
p0 = p;
for (size_t i = 0; i < s / 2; i++, p += POINT_DATA_SIZE) {
q = p0 + (s - i - 1) * POINT_DATA_SIZE;
memcpy(&pt, p, POINT_DATA_SIZE);
memcpy(p, q, POINT_DATA_SIZE);
memcpy(q, &pt, POINT_DATA_SIZE);
}
}
return false;
}
/**
Set this polygon's outer ring to be CCW and inner rings to be CW.
@return on error returns true, on success returns false.
*/
bool Gis_polygon::set_polygon_ring_order() {
DBUG_ASSERT(is_bg_adapter());
if (outer().set_ring_order(true /* Ring order: CCW. */)) return true;
Gis_polygon::inner_container_type::iterator itr;
Gis_polygon::inner_container_type &inns = inners();
for (itr = inns.begin(); itr != inns.end(); ++itr)
if (itr->set_ring_order(false /* Ring order: CW. */)) return true;
return false;
}
/**
Make outer ring and inner rings objects for this polygon if it doesn't
have one yet.
Outer ring and inner rings have to have separated memory space, because
we can't predict which one will be edited first. So the polygon object
doesn't directly have memory, its m_ptr points to the outer ring, its
m_inn_rings points to the inner rings, each have its own memory address
and length, and Gis_polygon::get_nbytes returns the sum of them.
If the polygon doesn't own memory, then there is only one piece of memory
passed into it and used by it, otherwise the two pieces of memory are
separately allocated and released.
*/
void Gis_polygon::make_rings() {
ring_type *outer = NULL;
if (this->m_ptr == NULL) {
outer = new ring_type(NULL, 0, Flags_t(wkb_linestring, 0), get_srid());
outer->set_owner(this);
this->m_ptr = outer;
}
if (m_inn_rings == NULL) {
m_inn_rings = new inner_container_type(
NULL, 0, Flags_t(wkb_polygon_inner_rings, 0), get_srid());
m_inn_rings->set_owner(this);
}
this->set_ownmem(false);
}
uint32 Gis_polygon::get_data_size() const {
if (check_stack_overrun(current_thd, STACK_MIN_SIZE, nullptr))
return GET_SIZE_ERROR;
uint32 n_linear_rings;
uint32 len;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (is_length_verified()) return get_nbytes();
/*
For a BG adapter polygon, its Gis_polygon::m_ptr points to its outer ring
rather than the WKB buffer, it is the only exception.
*/
DBUG_ASSERT(polygon_is_wkb_form() || !is_bg_adapter());
if (wkb.scan_non_zero_uint4(&n_linear_rings)) return GET_SIZE_ERROR;
while (n_linear_rings--) {
uint32 n_points;
if (wkb.scan_n_points_and_check_data(&n_points)) return GET_SIZE_ERROR;
wkb.skip_unsafe(n_points * POINT_DATA_SIZE);
}
len = static_cast<uint32>(wkb.data() - (const char *)get_data_ptr());
if (len != get_nbytes()) set_nbytes(len);
set_length_verified(true);
return len;
}
bool Gis_polygon::init_from_wkt(Gis_read_stream *trs, String *wkb) {
if (check_stack_overrun(trs->thd(), STACK_MIN_SIZE, nullptr)) return true;
uint32 n_linear_rings = 0;
uint32 lr_pos = wkb->length();
if (trs->check_next_symbol('(')) return true;
if (wkb->reserve(4, 512)) return true;
wkb->length(wkb->length() + 4); // Reserve space for points
bool is_first = true;
for (;;) {
Gis_line_string ls(false);
ls.set_props(is_first ? POLYGON_OUTER_RING : POLYGON_INNER_RING);
is_first = false;
if (ls.init_from_wkt(trs, wkb)) return true;
n_linear_rings++;
if (trs->skip_char(',')) // Didn't find ','
break;
}
write_at_position(lr_pos, n_linear_rings, wkb);
if (trs->check_next_symbol(')')) return true;
return false;
}
uint Gis_polygon::init_from_wkb(THD *thd, const char *wkb, uint len,
wkbByteOrder bo, String *res) {
if (check_stack_overrun(thd, STACK_MIN_SIZE, nullptr)) return 0;
uint32 n_linear_rings;
const char *wkb_orig = wkb;
if (len < 4) return 0;
if (0 == (n_linear_rings = wkb_get_uint(wkb, bo)) || res->reserve(4, 512))
return 0;
wkb += 4;
len -= 4;
q_append(n_linear_rings, res);
bool is_first = true;
while (n_linear_rings--) {
Gis_line_string ls(false);
ls.set_props(is_first ? POLYGON_OUTER_RING : POLYGON_INNER_RING);
is_first = false;
uint ls_len = 0;
if (!(ls_len = ls.init_from_wkb(thd, wkb, len, bo, res))) return 0;
wkb += ls_len;
DBUG_ASSERT(len >= ls_len);
len -= ls_len;
}
return (uint)(wkb - wkb_orig);
}
bool Gis_polygon::get_data_as_wkt(String *txt, wkb_parser *wkb) const {
uint32 n_linear_rings;
if (wkb->scan_non_zero_uint4(&n_linear_rings)) return true;
txt->append('(');
while (n_linear_rings--) {
uint32 n_points;
if (wkb->scan_n_points_and_check_data(&n_points) ||
txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points))
return true;
qs_append('(', txt);
append_points(txt, n_points, wkb, 0);
(*txt)[txt->length() - 1] = ')'; // Replace end ','
qs_append(',', txt);
}
txt->length(txt->length() - 1); // Remove end ','
qs_append(')', txt);
return false;
}
bool Gis_polygon::get_mbr(MBR *mbr, wkb_parser *wkb) const {
uint32 n_linear_rings;
if (wkb->scan_non_zero_uint4(&n_linear_rings)) return true;
while (n_linear_rings--) {
if (get_mbr_for_points(mbr, wkb, 0)) return true;
}
return false;
}
int Gis_polygon::exterior_ring(String *result) const {
uint32 n_points, n_linear_rings, length;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_non_zero_uint4(&n_linear_rings) ||
wkb.scan_n_points_and_check_data(&n_points))
return 1;
length = n_points * POINT_DATA_SIZE;
if (result->reserve(1 + 4 + 4 + length, 512)) return 1;
q_append((char)wkb_ndr, result);
q_append((uint32)wkb_linestring, result);
q_append(n_points, result);
q_append(wkb.data(), length, result);
return 0;
}
int Gis_polygon::num_interior_ring(uint32 *n_int_rings) const {
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_non_zero_uint4(n_int_rings)) return 1;
*n_int_rings -= 1;
return 0;
}
int Gis_polygon::interior_ring_n(uint32 num, String *result) const {
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
uint32 n_linear_rings;
uint32 n_points;
uint32 points_size;
if (num < 1 || wkb.scan_non_zero_uint4(&n_linear_rings) ||
num >= n_linear_rings)
return 1;
while (num--) {
if (wkb.scan_n_points_and_check_data(&n_points)) return 1;
wkb.skip_unsafe(n_points * POINT_DATA_SIZE);
}
if (wkb.scan_n_points_and_check_data(&n_points)) return 1;
points_size = n_points * POINT_DATA_SIZE;
if (result->reserve(1 + 4 + 4 + points_size, 512)) return 1;
q_append((char)wkb_ndr, result);
q_append((uint32)wkb_linestring, result);
q_append(n_points, result);
q_append(wkb.data(), points_size, result);
return 0;
}
bool Gis_polygon::reverse_coordinates() {
uint32 current_data_offset = 0;
uint32 numrings;
if (num_interior_ring(&numrings)) {
return true;
}
numrings += 1; // add exterior ring to number of rings.
current_data_offset += 4; // add numRings header size to data offset.
for (uint32 i = 0; i < numrings; i++) {
uint32 num_of_points = uint4korr(get_ucptr() + current_data_offset);
current_data_offset += 4; // add linear ring header size to data offset.
for (uint32 j = 0; j < num_of_points; j++) {
double x;
double y;
float8get(&x, get_cptr() + current_data_offset);
float8get(&y, get_cptr() + current_data_offset + SIZEOF_STORED_DOUBLE);
float8store(get_cptr() + current_data_offset, y);
float8store(get_cptr() + current_data_offset + SIZEOF_STORED_DOUBLE, x);
current_data_offset += POINT_DATA_SIZE;
}
}
return false;
}
bool Gis_polygon::validate_coordinate_range(double srs_angular_unit,
bool *long_out_of_range,
bool *lat_out_of_range,
double *out_of_range_value) {
uint32 numrings;
*long_out_of_range = false;
*lat_out_of_range = false;
if (num_interior_ring(&numrings)) {
return true; /* purecov: inspected */
}
numrings += 1; // Add exterior ring to number of rings.
uint32 current_data_offset = 4; // Add numRings header size to data offset.
for (uint32 i = 0; i < numrings; i++) {
uint32 num_of_points = uint4korr(get_ucptr() + current_data_offset);
current_data_offset += 4; // Add linear ring header size to data offset.
for (uint32 j = 0; j < num_of_points; j++) {
double x;
double y;
float8get(&x, get_cptr() + current_data_offset);
float8get(&y, get_cptr() + current_data_offset + SIZEOF_STORED_DOUBLE);
if (check_coordinate_range(x, y, srs_angular_unit, long_out_of_range,
lat_out_of_range, out_of_range_value)) {
return true;
}
current_data_offset += POINT_DATA_SIZE;
}
}
return false;
}
const Geometry::Class_info *Gis_polygon::get_class_info() const {
return &polygon_class;
}
/**
Packup a polygon's outer ring and inner rings into a single chunk of
memory as result. nbytes returns the number of bytes in WKB data.
The returned WKB has no WKB header.
Never call get_ptr to obtain a polygon's WKB data.
@param geo0 The polygon whose WKB data we want to pack up.
@param[out] pnbytes Takes back the number of bytes of the packed WKB string.
@return The address of the packed WKB string buffer.
*/
void *get_packed_ptr(const Geometry *geo0, size_t *pnbytes) {
DBUG_ASSERT(geo0->get_geotype() == Geometry::wkb_polygon && pnbytes != NULL);
const Gis_polygon *geo = static_cast<const Gis_polygon *>(geo0);
Gis_polygon::ring_type *out_ring = outer_ring(geo);
Gis_polygon::inner_container_type *inn_rings = geo->inner_rings();
size_t &nbytes = *pnbytes;
if (out_ring == NULL) {
DBUG_ASSERT(inn_rings == NULL);
*pnbytes = 0;
return NULL;
}
// Inner rings may have out of line rings.
if (inn_rings) inn_rings->reassemble();
size_t vallen = sizeof(uint32) + out_ring->get_nbytes() +
(inn_rings ? inn_rings->get_nbytes() : 0);
void *src_val = gis_wkb_alloc(vallen);
if (src_val == NULL) {
nbytes = 0;
return NULL;
}
memcpy(static_cast<char *>(src_val) + sizeof(uint32), out_ring->get_ptr(),
out_ring->get_nbytes());
size_t n_inns = 0;
if (inn_rings && inn_rings->get_nbytes()) {
memcpy(
static_cast<char *>(src_val) + sizeof(uint32) + out_ring->get_nbytes(),
inn_rings->get_ptr(), inn_rings->get_nbytes());
n_inns = inn_rings->size();
}
DBUG_ASSERT(1 + n_inns <= 0xFFFFFFFF);
int4store(static_cast<uchar *>(src_val), static_cast<uint32>(1 + n_inns));
nbytes = vallen;
return src_val;
}
/**
Get a polygon's WKB string's starting address. The polygon is already
packed so that its outer ring and inner rings point to different locations
of a continuous chunk of WKB buffer.
@param geo0 The already packed polygon, we want to get its data address.
@return The WKB string starting address, right after the WKB header if any.
*/
const char *get_packed_ptr(Geometry *geo0) {
DBUG_ASSERT(geo0->get_geotype() == Geometry::wkb_polygon);
Gis_polygon *geo = static_cast<Gis_polygon *>(geo0);
Gis_polygon::ring_type *out_ring = outer_ring(geo);
Gis_polygon::inner_container_type *inn_rings = geo->inner_rings();
if (inn_rings)
DBUG_ASSERT(out_ring->get_cptr() + out_ring->get_nbytes() ==
inn_rings->get_cptr());
return (out_ring->get_cptr() - sizeof(uint32) /*polygon's ring count */);
}
/**
Check whether plgn is packed into its owner mplgn's WKB buffer.
@param plgn the polygon to be checked
@param mplgn the multipolygon, owner/holder of plgn.
@return true if plgn is packed into mplgn, false otherwise.
*/
bool polygon_is_packed(Geometry *plgn, Geometry *mplgn) {
DBUG_ASSERT(plgn->get_geotype() == Geometry::wkb_polygon &&
mplgn->get_geotype() == Geometry::wkb_multipolygon);
Gis_polygon *geo = static_cast<Gis_polygon *>(plgn);
Gis_polygon::ring_type *out_ring = outer_ring(geo);
Gis_polygon::inner_container_type *inn_rings = geo->inner_rings();
const char *orstart = out_ring->get_cptr();
bool ret = false;
if (orstart < mplgn->get_cptr() + mplgn->get_nbytes() &&
orstart > mplgn->get_cptr()) {
// This polygon is already stored packed and inline
if (inn_rings && inn_rings->get_nbytes())
DBUG_ASSERT(orstart + out_ring->get_nbytes() == inn_rings->get_ptr());
ret = true;
}
return ret;
}
void own_rings(Geometry *geo0) {
DBUG_ASSERT(geo0->get_geotype() == Geometry::wkb_polygon);
Gis_polygon *geo = static_cast<Gis_polygon *>(geo0);
if (outer_ring(geo)) outer_ring(geo)->set_owner(geo);
if (geo->inner_rings()) geo->inner_rings()->set_owner(geo);
}
/***************************** MultiPoint *******************************/
uint32 Gis_multi_point::get_data_size() const {
if (check_stack_overrun(current_thd, STACK_MIN_SIZE, nullptr))
return GET_SIZE_ERROR;
uint32 n_points;
uint32 len;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (is_length_verified()) return get_nbytes();
if (wkb.scan_n_points_and_check_data(&n_points, WKB_HEADER_SIZE))
return GET_SIZE_ERROR;
len = 4 + n_points * (POINT_DATA_SIZE + WKB_HEADER_SIZE);
if (len != get_nbytes()) set_nbytes(len);
set_length_verified(true);
return len;
}
bool Gis_multi_point::init_from_wkt(Gis_read_stream *trs, String *wkb) {
if (check_stack_overrun(trs->thd(), STACK_MIN_SIZE, nullptr)) return true;
uint32 n_points = 0;
uint32 np_pos = wkb->length();
Gis_point p(false);
if (trs->check_next_symbol('(')) return true;
if (wkb->reserve(4, 512)) return true;
wkb->length(wkb->length() + 4); // Reserve space for points
/*
According to OGC, the WKT for a multipoint is something like:
MULTIPOINT((1 1), (2 2))
and to be backward compatible with older versions of MySQL, we still
support the MySQL format:
MULTIPOINT(1 1, 2 2)
But we don't allow the mixture of both formats in the same multipoint
geometry.
*/
const bool match_pt_lbra =
(trs->get_next_toc_type() == Gis_read_stream::l_bra);
for (;;) {
if (wkb->reserve(1 + 4, 512)) return 1;
q_append((char)wkb_ndr, wkb);
q_append((uint32)wkb_point, wkb);
if (match_pt_lbra && trs->check_next_symbol('(')) return true;
if (p.init_from_wkt(trs, wkb, false)) return true;
if (match_pt_lbra && trs->check_next_symbol(')')) return true;
n_points++;
if (trs->skip_char(',')) // Didn't find ','
break;
}
write_at_position(np_pos, n_points, wkb); // Store number of found points
if (trs->check_next_symbol(')')) return true;
return false;
}
uint Gis_multi_point::init_from_wkb(THD *thd, const char *wkb, uint len,
wkbByteOrder bo, String *res) {
if (check_stack_overrun(thd, STACK_MIN_SIZE, nullptr)) return 0;
uint32 n_points;
uint proper_size;
Gis_point p(false);
const char *wkb_end;
if (len < 4 || (n_points = wkb_get_uint(wkb, bo)) > max_n_points) return 0;
proper_size = 4 + n_points * (WKB_HEADER_SIZE + POINT_DATA_SIZE);
if (len < proper_size || res->reserve(proper_size, 512)) return 0;
q_append(n_points, res);
wkb_end = wkb + proper_size;
for (wkb += 4; wkb < wkb_end; wkb += (WKB_HEADER_SIZE + POINT_DATA_SIZE)) {
write_wkb_header(res, wkb_point);
if ((*wkb != wkb_xdr && *wkb != wkb_ndr) ||
wkb_point != uint4korr(wkb + 1) ||
!p.init_from_wkb(thd, wkb + WKB_HEADER_SIZE, POINT_DATA_SIZE,
(wkbByteOrder)wkb[0], res))
return 0;
}
return proper_size;
}
bool Gis_multi_point::get_data_as_wkt(String *txt, wkb_parser *wkb) const {
uint32 n_points;
txt->append('(');
if (wkb->scan_n_points_and_check_data(&n_points, WKB_HEADER_SIZE) ||
txt->reserve(((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points))
return true;
/*
Now we will output multipoint in OGC format, i.e. for each of its point,
bracket its coordinates with ().
*/
append_points(txt, n_points, wkb, WKB_HEADER_SIZE, true);
txt->length(txt->length() - 1); // Remove end ','
qs_append(')', txt);
return false;
}
bool Gis_multi_point::get_mbr(MBR *mbr, wkb_parser *wkb) const {
return get_mbr_for_points(mbr, wkb, WKB_HEADER_SIZE);
}
int Gis_multi_point::num_geometries(uint32 *num) const {
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
return wkb.scan_non_zero_uint4(num) ? 1 : 0;
}
int Gis_multi_point::geometry_n(uint32 num, String *result) const {
uint32 n_points;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (num < 1 || wkb.scan_n_points_and_check_data(&n_points, WKB_HEADER_SIZE) ||
num > n_points || result->reserve(WKB_HEADER_SIZE + POINT_DATA_SIZE, 32))
return 1;
wkb.skip_unsafe((num - 1) * (WKB_HEADER_SIZE + POINT_DATA_SIZE));
q_append(wkb.data(), WKB_HEADER_SIZE + POINT_DATA_SIZE, result);
return 0;
}
bool Gis_multi_point::reverse_coordinates() {
uint32 current_data_offset = 0;
uint32 num_of_points;
if (num_geometries(&num_of_points)) {
return true;
}
current_data_offset += 4; // add number of points header to offset.
for (uint32 i = 0; i < num_of_points; i++) {
double x;
double y;
current_data_offset +=
WKB_HEADER_SIZE; // since each point includes a header.
float8get(&x, get_cptr() + current_data_offset);
float8get(&y, get_cptr() + current_data_offset + SIZEOF_STORED_DOUBLE);
float8store(get_cptr() + current_data_offset, y);
float8store(get_cptr() + current_data_offset + SIZEOF_STORED_DOUBLE, x);
current_data_offset += POINT_DATA_SIZE;
}
return false;
}
bool Gis_multi_point::validate_coordinate_range(double srs_angular_unit,
bool *long_out_of_range,
bool *lat_out_of_range,
double *out_of_range_value) {
uint32 num_of_points;
*long_out_of_range = false;
*lat_out_of_range = false;
if (num_geometries(&num_of_points)) {
return true; /* purecov: inspected */
}
uint32 current_data_offset = 4; // Add number of points header to offset.
for (uint32 i = 0; i < num_of_points; i++) {
double x;
double y;
current_data_offset +=
WKB_HEADER_SIZE; // Since each point includes a header.
float8get(&x, get_cptr() + current_data_offset);
float8get(&y, get_cptr() + current_data_offset + SIZEOF_STORED_DOUBLE);
if (check_coordinate_range(x, y, srs_angular_unit, long_out_of_range,
lat_out_of_range, out_of_range_value)) {
return true;
}
current_data_offset += POINT_DATA_SIZE;
}
return false;
}
const Geometry::Class_info *Gis_multi_point::get_class_info() const {
return &multipoint_class;
}
/***************************** MultiLineString *******************************/
uint32 Gis_multi_line_string::get_data_size() const {
if (check_stack_overrun(current_thd, STACK_MIN_SIZE, nullptr))
return GET_SIZE_ERROR;
uint32 n_line_strings;
uint32 len;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (is_length_verified()) return get_nbytes();
if (wkb.scan_non_zero_uint4(&n_line_strings)) return GET_SIZE_ERROR;
while (n_line_strings--) {
uint32 n_points;
if (wkb.skip_wkb_header() || wkb.scan_n_points_and_check_data(&n_points))
return GET_SIZE_ERROR;
wkb.skip_unsafe(n_points * POINT_DATA_SIZE);
}
len = static_cast<uint32>(wkb.data() - (const char *)get_data_ptr());
if (len != get_nbytes()) set_nbytes(len);
set_length_verified(true);
return len;
}
bool Gis_multi_line_string::init_from_wkt(Gis_read_stream *trs, String *wkb) {
if (check_stack_overrun(trs->thd(), STACK_MIN_SIZE, nullptr)) return true;
uint32 n_line_strings = 0;
uint32 ls_pos = wkb->length();
if (trs->check_next_symbol('(')) return true;
if (wkb->reserve(4, 512)) return true;
wkb->length(wkb->length() + 4); // Reserve space for points
for (;;) {
Gis_line_string ls(false);
if (wkb->reserve(1 + 4, 512)) return true;
q_append((char)wkb_ndr, wkb);
q_append((uint32)wkb_linestring, wkb);
if (ls.init_from_wkt(trs, wkb)) return true;
n_line_strings++;
if (trs->skip_char(',')) // Didn't find ','
break;
}
write_at_position(ls_pos, n_line_strings, wkb);
if (trs->check_next_symbol(')')) return true;
return false;
}
uint Gis_multi_line_string::init_from_wkb(THD *thd, const char *wkb, uint len,
wkbByteOrder bo, String *res) {
if (check_stack_overrun(thd, STACK_MIN_SIZE, nullptr)) return 0;
uint32 n_line_strings;
const char *wkb_orig = wkb;
if (len < 4 || (n_line_strings = wkb_get_uint(wkb, bo)) < 1) return 0;
if (res->reserve(4, 512)) return 0;
q_append(n_line_strings, res);
wkb += 4;
len -= 4;
while (n_line_strings--) {
Gis_line_string ls(false);
uint ls_len = 0;
if ((len < WKB_HEADER_SIZE) || uint4korr(wkb + 1) != wkb_linestring ||
(*wkb != wkb_xdr && *wkb != wkb_ndr) ||
res->reserve(WKB_HEADER_SIZE, 512))
return 0;
write_wkb_header(res, wkb_linestring);
if (!(ls_len = ls.init_from_wkb(thd, wkb + WKB_HEADER_SIZE,
len - WKB_HEADER_SIZE, (wkbByteOrder)wkb[0],
res)))
return 0;
ls_len += WKB_HEADER_SIZE;
;
wkb += ls_len;
DBUG_ASSERT(len >= ls_len);
len -= ls_len;
}
return (uint)(wkb - wkb_orig);
}
bool Gis_multi_line_string::get_data_as_wkt(String *txt,
wkb_parser *wkb) const {
uint32 n_line_strings;
if (wkb->scan_non_zero_uint4(&n_line_strings)) return true;
txt->append('(');
while (n_line_strings--) {
uint32 n_points;
if (wkb->skip_wkb_header() ||
wkb->scan_n_points_and_check_data(&n_points) ||
txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points))
return true;
qs_append('(', txt);
append_points(txt, n_points, wkb, 0);
(*txt)[txt->length() - 1] = ')';
qs_append(',', txt);
}
txt->length(txt->length() - 1);
qs_append(')', txt);
return false;
}
bool Gis_multi_line_string::get_mbr(MBR *mbr, wkb_parser *wkb) const {
uint32 n_line_strings;
if (wkb->scan_non_zero_uint4(&n_line_strings)) return true;
while (n_line_strings--) {
if (wkb->skip_wkb_header() || get_mbr_for_points(mbr, wkb, 0)) return true;
}
return false;
}
int Gis_multi_line_string::num_geometries(uint32 *num) const {
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
return wkb.scan_non_zero_uint4(num) ? 1 : 0;
}
int Gis_multi_line_string::geometry_n(uint32 num, String *result) const {
uint32 n_line_strings, n_points, length;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_non_zero_uint4(&n_line_strings)) return 1;
if ((num > n_line_strings) || (num < 1)) return 1;
for (;;) {
if (wkb.skip_wkb_header() || wkb.scan_n_points_and_check_data(&n_points))
return 1;
length = POINT_DATA_SIZE * n_points;
if (!--num) break;
wkb.skip_unsafe(length);
}
return result->append(wkb.data() - 4 - WKB_HEADER_SIZE,
length + 4 + WKB_HEADER_SIZE, static_cast<size_t>(0));
}
int Gis_multi_line_string::geom_length(double *len) const {
uint32 n_line_strings;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_non_zero_uint4(&n_line_strings)) return 1;
*len = 0;
while (n_line_strings--) {
double ls_len;
Gis_line_string ls(false);
if (wkb.skip_wkb_header()) return 1;
ls.set_data_ptr(&wkb);
if (ls.geom_length(&ls_len)) return 1;
*len += ls_len;
/*
We know here that ls was ok, so we can call the trivial function
Gis_line_string::get_data_size without error checking.
*/
wkb.skip_unsafe(ls.get_data_size());
}
return 0;
}
int Gis_multi_line_string::is_closed(int *closed) const {
uint32 n_line_strings;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (wkb.scan_non_zero_uint4(&n_line_strings)) return 1;
while (n_line_strings--) {
Gis_line_string ls(false);
if (wkb.skip_wkb_header()) return 1;
ls.set_data_ptr(&wkb);
if (ls.is_closed(closed)) return 1;
if (!*closed) return 0;
wkb.skip_unsafe(ls.get_data_size());
}
return 0;
}
bool Gis_multi_line_string::reverse_coordinates() {
uint32 num_of_linestrings;
size_t current_data_offset = 4; // Skip num_wkbLineStrings header size.
String str(get_cptr(), get_nbytes(), &my_charset_bin);
if (num_geometries(&num_of_linestrings)) {
return true;
}
for (uint32 i = 1; i <= num_of_linestrings; i++) {
String result;
if (geometry_n(i, &result)) {
return true;
}
Geometry *g;
Geometry_buffer buffer;
if (!(g = Geometry::construct(&buffer, &result, false))) {
return true;
}
if (g->reverse_coordinates()) {
return true;
}
if (str.replace(current_data_offset, result.length(), result.ptr(),
result.length())) {
return true;
}
current_data_offset += result.length();
}
return false;
}
bool Gis_multi_line_string::validate_coordinate_range(
double srs_angular_unit, bool *long_out_of_range, bool *lat_out_of_range,
double *out_of_range_value) {
uint32 num_of_linestrings;
*long_out_of_range = false;
*lat_out_of_range = false;
if (num_geometries(&num_of_linestrings)) {
return true; /* purecov: inspected */
}
for (uint32 i = 1; i <= num_of_linestrings; i++) {
String result;
if (geometry_n(i, &result)) {
return true; /* purecov: inspected */
}
Geometry *g;
Geometry_buffer buffer;
if (!(g = Geometry::construct(&buffer, &result, false))) {
return true; /* purecov: inspected */
}
if (g->validate_coordinate_range(srs_angular_unit, long_out_of_range,
lat_out_of_range, out_of_range_value)) {
return true;
}
}
return false;
}
const Geometry::Class_info *Gis_multi_line_string::get_class_info() const {
return &multilinestring_class;
}
/***************************** MultiPolygon *******************************/
uint32 Gis_multi_polygon::get_data_size() const {
if (check_stack_overrun(current_thd, STACK_MIN_SIZE, nullptr))
return GET_SIZE_ERROR;
uint32 n_polygons;
uint32 len;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
if (is_length_verified()) return get_nbytes();
if (wkb.scan_non_zero_uint4(&n_polygons)) return GET_SIZE_ERROR;
while (n_polygons--) {
uint32 n_linear_rings;
if (wkb.skip_wkb_header() || wkb.scan_non_zero_uint4(&n_linear_rings))
return GET_SIZE_ERROR;
while (n_linear_rings--) {
uint32 n_points;
if (wkb.scan_n_points_and_check_data(&n_points)) return GET_SIZE_ERROR;
wkb.skip_unsafe(n_points * POINT_DATA_SIZE);
}
}
len = static_cast<uint32>(wkb.data() - (const char *)get_data_ptr());
if (len != get_nbytes()) set_nbytes(len);
set_length_verified(true);
return len;
}
bool Gis_multi_polygon::init_from_wkt(Gis_read_stream *trs, String *wkb) {
if (check_stack_overrun(trs->thd(), STACK_MIN_SIZE, nullptr)) return true;
uint32 n_polygons = 0;
uint32 np_pos = wkb->length();
Gis_polygon p(false);
if (trs->check_next_symbol('(')) return true;
if (wkb->reserve(4, 512)) return true;
wkb->length(wkb->length() + 4); // Reserve space for points
for (;;) {
if (wkb->reserve(1 + 4, 512)) return true;
q_append((char)wkb_ndr, wkb);
q_append((uint32)wkb_polygon, wkb);
if (p.init_from_wkt(trs, wkb)) return true;
n_polygons++;
if (trs->skip_char(',')) // Didn't find ','
break;
}
write_at_position(np_pos, n_polygons, wkb);
if (trs->check_next_symbol(')')) return true;
return false;
}
uint Gis_multi_polygon::init_from_wkb(THD *thd, const char *wkb, uint len,
wkbByteOrder bo, String *res) {
if (check_stack_overrun(thd, STACK_MIN_SIZE, nullptr)) return 0;
uint32 n_poly;
const char *wkb_orig = wkb;
if (len < 4) return 0;
n_poly = wkb_get_uint(wkb, bo);
if (res->reserve(4, 512)) return 0;
q_append(n_poly, res);
wkb += 4;
len -= 4;
while (n_poly--) {
Gis_polygon p(false);
uint p_len = 0;
if (len < WKB_HEADER_SIZE || uint4korr(wkb + 1) != wkb_polygon ||
(*wkb != wkb_xdr && *wkb != wkb_ndr) ||
res->reserve(WKB_HEADER_SIZE, 512))
return 0;
write_wkb_header(res, wkb_polygon);
if (!(p_len =
p.init_from_wkb(thd, wkb + WKB_HEADER_SIZE, len - WKB_HEADER_SIZE,
(wkbByteOrder)wkb[0], res)))
return 0;
p_len += WKB_HEADER_SIZE;
wkb += p_len;
DBUG_ASSERT(len >= p_len);
len -= p_len;
}
return (uint)(wkb - wkb_orig);
}
bool Gis_multi_polygon::get_data_as_wkt(String *txt, wkb_parser *wkb) const {
uint32 n_polygons;
if (wkb->scan_non_zero_uint4(&n_polygons)) return true;
txt->append('(');
while (n_polygons--) {
uint32 n_linear_rings;
if (wkb->skip_wkb_header() || wkb->scan_non_zero_uint4(&n_linear_rings) ||
txt->reserve(1, 512))
return true;
q_append('(', txt);
while (n_linear_rings--) {
uint32 n_points;
if (wkb->scan_n_points_and_check_data(&n_points) ||
txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points))
return true;
qs_append('(', txt);
append_points(txt, n_points, wkb, 0);
(*txt)[txt->length() - 1] = ')';
qs_append(',', txt);
}
(*txt)[txt->length() - 1] = ')';
qs_append(',', txt);
}
txt->length(txt->length() - 1);
qs_append(')', txt);
return false;
}
bool Gis_multi_polygon::get_mbr(MBR *mbr, wkb_parser *wkb) const {
uint32 n_polygons;
if (wkb->scan_non_zero_uint4(&n_polygons)) return true;
while (n_polygons--) {
uint32 n_linear_rings;
if (wkb->skip_wkb_header() || wkb->scan_non_zero_uint4(&n_linear_rings))
return true;
while (n_linear_rings--) {
if (get_mbr_for_points(mbr, wkb, 0)) return true;
}
}
return false;
}
int Gis_multi_polygon::num_geometries(uint32 *num) const {
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
return wkb.scan_non_zero_uint4(num) ? 1 : 0;
}
int Gis_multi_polygon::geometry_n(uint32 num, String *result) const {
uint32 n_polygons;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
const char *start_of_polygon = wkb.data();
if (wkb.scan_non_zero_uint4(&n_polygons)) return 1;
if (num > n_polygons || num < 1) return -1;
do {
uint32 n_linear_rings;
start_of_polygon = wkb.data();
if (wkb.skip_wkb_header() || wkb.scan_non_zero_uint4(&n_linear_rings))
return 1;
while (n_linear_rings--) {
uint32 n_points;
if (wkb.scan_n_points_and_check_data(&n_points)) return 1;
wkb.skip_unsafe(POINT_DATA_SIZE * n_points);
}
} while (--num);
if (wkb.no_data(0)) // We must check last segment
return 1;
return result->append(start_of_polygon,
(uint32)(wkb.data() - start_of_polygon),
static_cast<size_t>(0));
}
bool Gis_multi_polygon::reverse_coordinates() {
uint32 num_of_polygons;
size_t current_data_offset = 4; // Skip num_polygons header size.
String str(get_cptr(), get_nbytes(), &my_charset_bin);
if (num_geometries(&num_of_polygons)) {
return true;
}
for (uint32 i = 1; i <= num_of_polygons; i++) {
String result;
if (geometry_n(i, &result)) {
return true;
}
Geometry *g;
Geometry_buffer buffer;
if (!(g = Geometry::construct(&buffer, &result, false))) {
return true;
}
if (g->reverse_coordinates()) {
return true;
}
if (str.replace(current_data_offset, result.length(), result.ptr(),
result.length())) {
return true;
}
current_data_offset += result.length();
}
return false;
}
bool Gis_multi_polygon::validate_coordinate_range(double srs_angular_unit,
bool *long_out_of_range,
bool *lat_out_of_range,
double *out_of_range_value) {
uint32 num_of_polygons;
*long_out_of_range = false;
*lat_out_of_range = false;
if (num_geometries(&num_of_polygons)) {
return true; /* purecov: inspected */
}
for (uint32 i = 1; i <= num_of_polygons; i++) {
String result;
if (geometry_n(i, &result)) {
return true; /* purecov: inspected */
}
Geometry *g;
Geometry_buffer buffer;
if (!(g = Geometry::construct(&buffer, &result, false))) {
return true; /* purecov: inspected */
}
if (g->validate_coordinate_range(srs_angular_unit, long_out_of_range,
lat_out_of_range, out_of_range_value)) {
return true;
}
}
return false;
}
const Geometry::Class_info *Gis_multi_polygon::get_class_info() const {
return &multipolygon_class;
}
/************************* GeometryCollection ****************************/
/**
Create a Geometry object from WKB.
This function creates an intermediate geometry object which may have wrong
length property (longer than what's needed by the geometry), use it only
within this class. Do not check for exact length here, caller will do
that if necessary.
@param wkb The input WKB.
@param buffer A buffer for the output geometry.
@return NULL if the input WKB was found invalid. Otherwise, the constructed
geometry.
*/
Geometry *Gis_geometry_collection::scan_header_and_create(
wkb_parser *wkb, Geometry_buffer *buffer) {
Geometry *geom;
wkb_header header;
if (wkb->scan_wkb_header(&header) ||
!(geom = create_by_typeid(buffer, header.wkb_type)))
return NULL;
geom->set_data_ptr(wkb->data(), wkb->length());
/*
The length in geom might be wrong, since it's set to the total length of
the geometry collection's WKB. Such error is only allowed for temporary
geometry objects created here.
Correct the length only for points because other types has known structure
and can deduce the valid length. But for point we have to always require
the exact length.
*/
if (geom->get_type() == wkb_point) {
if (geom->get_nbytes() < POINT_DATA_SIZE) return nullptr;
geom->set_nbytes(POINT_DATA_SIZE);
}
return geom;
}
/**
Append geometry into geometry collection which can be empty.
@param geo geometry to be appended, it can't be empty.
@param gcbuf this geometry collection's data buffer, it's of GEOMETRY format
and is a separate String buffer.
@return false if no error, otherwise true.
*/
bool Gis_geometry_collection::append_geometry(const Geometry *geo,
String *gcbuf) {
uint32 collection_len = gcbuf->length(), geo_len = geo->get_data_size();
if (geo_len == GET_SIZE_ERROR) return true;
DBUG_ASSERT(collection_len == 0 ||
get_data_size() == collection_len - GEOM_HEADER_SIZE);
if (gcbuf->reserve((collection_len == 0 ? GEOM_HEADER_SIZE + 4 : 0) +
geo_len + WKB_HEADER_SIZE,
512))
return true;
char *ptr = gcbuf->ptr();
uint32 extra = 0;
if (collection_len == 0) {
collection_len = GEOM_HEADER_SIZE + 4;
extra = GEOM_HEADER_SIZE;
write_geometry_header(ptr, geo->get_srid(), wkb_geometrycollection, 0);
set_srid(geo->get_srid());
has_geom_header_space(true);
}
// Skip GEOMETRY header.
ptr += GEOM_HEADER_SIZE;
const char *start = ptr;
int4store(ptr, uint4korr(ptr) + 1); // Increment object count.
ptr += collection_len - GEOM_HEADER_SIZE;
ptr = write_wkb_header(ptr, geo->get_type());
memcpy(ptr, geo->get_data_ptr(), geo_len);
gcbuf->length(collection_len + geo_len + WKB_HEADER_SIZE);
set_data_ptr(start, extra + collection_len + geo_len - SRID_SIZE);
return false;
}
/**
Append geometry into geometry collection, which can be empty. This object
must be created from default constructor or below one:
Gis_geometry_collection(gis::srid_t srid, wkbType gtype,
const String *gbuf,
String *gcbuf);
@param srid srid of geometry to be appended.
@param gtype type of geometry to be appended.
@param gbuf WKB data of geometry to be appended, gbuf->ptr isn't NULL and
points right after the WKB header, this buffer can't be empty.
@param gcbuf this geometry collection's data buffer, it's of GEOMETRY format
and is a separate String buffer.
@return false if no error, otherwise true.
*/
bool Gis_geometry_collection::append_geometry(gis::srid_t srid, wkbType gtype,
const String *gbuf,
String *gcbuf) {
DBUG_ASSERT(gbuf != NULL && gbuf->ptr() != NULL && gbuf->length() > 0);
uint32 collection_len = gcbuf->length(), geo_len = gbuf->length();
DBUG_ASSERT(collection_len == 0 ||
get_data_size() == collection_len - GEOM_HEADER_SIZE);
if (gcbuf->reserve((collection_len == 0 ? GEOM_HEADER_SIZE + 4 : 0) +
geo_len + WKB_HEADER_SIZE,
512))
return true;
char *ptr = gcbuf->ptr();
uint32 extra = 0;
if (collection_len == 0) {
collection_len = GEOM_HEADER_SIZE + 4;
extra = GEOM_HEADER_SIZE;
write_geometry_header(ptr, srid, wkb_geometrycollection, 0);
set_srid(srid);
has_geom_header_space(true);
} else if (srid != get_srid())
return true;
// Skip GEOMETRY header.
ptr += GEOM_HEADER_SIZE;
const char *start = ptr;
int4store(ptr, uint4korr(ptr) + 1); // Increment object count.
ptr += collection_len - GEOM_HEADER_SIZE;
ptr = write_wkb_header(ptr, gtype);
memcpy(ptr, gbuf->ptr(), geo_len);
gcbuf->length(collection_len + geo_len + WKB_HEADER_SIZE);
set_data_ptr(start, extra + collection_len + geo_len - SRID_SIZE);
return false;
}
/**
Create a geometry collection from a single geometry, and the created object
refers to position right after the WKB header inside the 'gcbuf' buffer.
@param srid the SRID of the first geometry to put into this
geometry collection. Its SRID is used as the SRID of this
geometry collection.
@param gtype the type of the first geometry to put into this object.
@param gbuf stores the WKB data of the first geometry to put into this object,
not including its WKB header. if gbuf is NULL or gbuf->ptr is
NULL, the created geometry collection is empty.
@param gcbuf this geometry collection's data buffer in GEOMETRY format.
*/
Gis_geometry_collection::Gis_geometry_collection(gis::srid_t srid,
wkbType gtype,
const String *gbuf,
String *gcbuf)
: Geometry(0, 0, Flags_t(wkb_geometrycollection, 0), srid) {
uint32 geo_len = gbuf ? gbuf->length() : 0, total_len = 0;
DBUG_ASSERT((gbuf == NULL || (gbuf->ptr() == NULL && gbuf->length() == 0)) ||
(gbuf->ptr() != NULL && gbuf->length() > 0));
total_len = geo_len + sizeof(uint32) /*NUM-objs*/ + SRID_SIZE +
WKB_HEADER_SIZE + (geo_len > 0 ? WKB_HEADER_SIZE : 0);
// Reserve 512 bytes extra space for geometries to be appended later,
// to avoid some reallocations.
if (gcbuf->reserve(total_len + 512, 1024))
my_error(ER_OUTOFMEMORY, total_len + 512);
char *ptr = gcbuf->ptr();
const char *start = ptr + GEOM_HEADER_SIZE;
ptr = write_geometry_header(ptr, srid, Geometry::wkb_geometrycollection,
geo_len ? 1 : 0);
if (geo_len > 0) {
ptr = write_wkb_header(ptr, gtype);
memcpy(ptr, gbuf->ptr(), geo_len);
}
gcbuf->length(total_len);
set_data_ptr(start, total_len - GEOM_HEADER_SIZE);
set_srid(srid);
has_geom_header_space(true);
}
/**
Create a geometry collection from a single geometry, and this object refer
to position right after the WKB header inside the 'gcbuf' buffer.
@param geo the first valid geometry to put into this geometry collection.
Its SRID is used as the SRID of this geometry collection. It must be
a valid geometry.
@param gcbuf this geometry collection's data buffer in GEOMETRY format.
*/
Gis_geometry_collection::Gis_geometry_collection(Geometry *geo, String *gcbuf)
: Geometry(0, 0, Flags_t(wkb_geometrycollection, 0), geo->get_srid()) {
DBUG_ASSERT(geo != NULL && geo->get_ptr() != NULL);
uint32 geo_len = geo->get_data_size(), total_len = 0;
DBUG_ASSERT(geo_len != GET_SIZE_ERROR);
total_len =
geo_len + sizeof(uint32) /*NUM-objs*/ + SRID_SIZE + WKB_HEADER_SIZE * 2;
// Reserve 512 bytes extra space for geometries to be appended later,
// to avoid some reallocations.
if (gcbuf->reserve(total_len + 512, 1024))
my_error(ER_OUTOFMEMORY, total_len + 512);
char *ptr = gcbuf->ptr();
const char *start = ptr + GEOM_HEADER_SIZE;
ptr = write_geometry_header(ptr, geo->get_srid(),
Geometry::wkb_geometrycollection, 1);
ptr = write_wkb_header(ptr, geo->get_type());
memcpy(ptr, geo->get_data_ptr(), geo_len);
gcbuf->length(total_len);
set_data_ptr(start, total_len - GEOM_HEADER_SIZE);
set_srid(geo->get_srid());
has_geom_header_space(true);
}
uint32 Gis_geometry_collection::get_data_size() const {
if (check_stack_overrun(current_thd, STACK_MIN_SIZE, nullptr))
return GET_SIZE_ERROR;
uint32 n_objects = 0;
uint32 len;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
Geometry_buffer buffer;
Geometry *geom;
if (is_length_verified()) return get_nbytes();
/*
We allow a geometry collection of 0 components, because this is how we
define an 'empty collection', which is used as the result of set operation
that returns an empty result, it's different from NULL value just in the
same way an empty string is different from a NULL.
*/
if (wkb.scan_non_zero_uint4(&n_objects) && n_objects != 0)
return GET_SIZE_ERROR;
while (n_objects--) {
if (!(geom = scan_header_and_create(&wkb, &buffer))) return GET_SIZE_ERROR;
uint32 object_size;
/*
'geom' is a temporary object whose length may be wrongly specified by
scan_header_and_create() function, so here we don't require
object_size + GEOM_HEADER_SIZE == wkb->length()
*/
if ((object_size = geom->get_data_size()) == GET_SIZE_ERROR)
return GET_SIZE_ERROR;
/*
Use 'skip()' instead of 'skip_unsafe()' in case the object size is
incorrect
*/
if (wkb.skip(object_size)) {
DBUG_ASSERT(false); // geom-get_data_size() did something wrong.
return GET_SIZE_ERROR;
}
}
len = static_cast<uint32>(wkb.data() - (const char *)get_data_ptr());
if (len != get_nbytes()) set_nbytes(len);
set_length_verified(true);
return len;
}
bool Gis_geometry_collection::init_from_wkt(Gis_read_stream *trs, String *wkb) {
if (check_stack_overrun(trs->thd(), STACK_MIN_SIZE, nullptr)) return true;
uint32 n_objects = 0;
uint32 no_pos = wkb->length();
Geometry_buffer buffer;
Geometry *g;
if (wkb->reserve(4, 512)) return true;
wkb->length(wkb->length() + 4); // Reserve space for points
if (trs->get_next_toc_type() == Gis_read_stream::word) {
LEX_CSTRING empty;
if (trs->get_next_word(&empty) || empty.length != 5 ||
native_strncasecmp("EMPTY", empty.str, 5))
return true;
} else {
if (trs->check_next_symbol('(')) return true;
for (;;) {
/*
Allow specifying an empty geometry collection in this form:
'geometrycollection()'.
*/
if (n_objects == 0 && trs->get_next_toc_type() == Gis_read_stream::r_bra)
break;
if (!(g = create_from_wkt(&buffer, trs, wkb, true,
false /* Allow trailing bytes. */)))
return true;
/*
Allow g to be a nested geometry collection, nested ones are flatterned
in BG_geometry_collection before sending to new BG based GIS algorithms.
*/
n_objects++;
if (trs->skip_char(',')) // Didn't find ','
break;
}
if (trs->check_next_symbol(')')) return true;
}
write_at_position(no_pos, n_objects, wkb);
return false;
}
uint Gis_geometry_collection::init_from_wkb(THD *thd, const char *wkb, uint len,
wkbByteOrder bo, String *res) {
if (check_stack_overrun(thd, STACK_MIN_SIZE, nullptr)) return 0;
uint32 n_geom = 0;
const char *wkb_orig = wkb;
if (len < 4) return 0;
n_geom = wkb_get_uint(wkb, bo);
if (res->reserve(4, 512)) return 0;
q_append(n_geom, res);
wkb += 4;
len -= 4;
/* Allow 0 components as an empty collection. */
while (n_geom--) {
Geometry_buffer buffer;
Geometry *geom;
uint g_len = 0;
uint32 wkb_type;
if (len < WKB_HEADER_SIZE || (*wkb != wkb_xdr && *wkb != wkb_ndr) ||
res->reserve(WKB_HEADER_SIZE, 512))
return 0;
wkb_type = wkb_get_uint(wkb + 1, (wkbByteOrder)wkb[0]);
write_wkb_header(res, static_cast<wkbType>(wkb_type));
if (!(geom = create_by_typeid(&buffer, wkb_type)) ||
!(g_len = geom->init_from_wkb(thd, wkb + WKB_HEADER_SIZE,
len - WKB_HEADER_SIZE,
(wkbByteOrder)wkb[0], res)))
return 0;
g_len += WKB_HEADER_SIZE;
wkb += g_len;
DBUG_ASSERT(len >= g_len);
len -= g_len;
}
return (uint)(wkb - wkb_orig);
}
bool Gis_geometry_collection::get_data_as_wkt(String *txt,
wkb_parser *wkb) const {
uint32 n_objects = 0;
Geometry_buffer buffer;
Geometry *geom;
size_t nback = 1;
/* Allow 0 components as an empty collection. */
if (wkb->scan_non_zero_uint4(&n_objects) && n_objects != 0) return true;
if (n_objects == 0) {
txt->append(STRING_WITH_LEN(" EMPTY"));
} else {
txt->append('(');
while (n_objects--) {
if (!(geom = scan_header_and_create(wkb, &buffer)) ||
geom->as_wkt(txt, wkb) || txt->append(STRING_WITH_LEN(","), 512))
return true;
}
txt->length(txt->length() - nback);
txt->append(')');
}
return false;
}
bool Gis_geometry_collection::get_mbr(MBR *mbr, wkb_parser *wkb) const {
uint32 n_objects;
Geometry_buffer buffer;
Geometry *geom;
/* An empty collection's MBR is NULL. */
if (wkb->scan_non_zero_uint4(&n_objects)) return true;
bool found_one = false;
while (n_objects--) {
if (!(geom = scan_header_and_create(wkb, &buffer)) ||
geom->get_mbr(mbr, wkb)) {
/*
An empty collection should be simply skipped, it may contain a tree
of empty collections which is still empty.
*/
if (geom != NULL && geom->get_type() == wkb_geometrycollection) continue;
return true;
}
// Now we've found a solid component and updated the mbr.
found_one = true;
}
/* An collection containing only a few empty collections, the MBR is NULL. */
if (!found_one) return true;
return false;
}
int Gis_geometry_collection::num_geometries(uint32 *num) const {
*num = 0;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
/* Should return 0 and *num=0 if called with an empty collection. */
return (wkb.scan_non_zero_uint4(num) && *num != 0) ? 1 : 0;
}
int Gis_geometry_collection::geometry_n(uint32 num, String *result) const {
uint32 n_objects, length;
wkb_parser wkb(get_cptr(), get_cptr() + get_nbytes());
Geometry_buffer buffer;
Geometry *geom;
/* It's an error to call this with an empty collection. */
if (wkb.scan_non_zero_uint4(&n_objects)) return 1;
if (num > n_objects || num < 1) return 1;
wkb_header header;
do {
if (wkb.scan_wkb_header(&header) ||
!(geom = create_by_typeid(&buffer, header.wkb_type)))
return 1;
// Must set precise length for points even for a temporary/intermediate one.
if (geom->get_type() == wkb_point)
geom->set_data_ptr(wkb.data(), POINT_DATA_SIZE);
else
geom->set_data_ptr(&wkb);
if ((length = geom->get_data_size()) == GET_SIZE_ERROR) return 1;
wkb.skip_unsafe(length);
} while (--num);
/* Copy found object to result */
if (result->reserve(1 + 4 + length, 512)) return 1;
q_append((char)wkb_ndr, result);
q_append(header.wkb_type, result);
q_append(wkb.data() - length, length, result); // data-length= start_of_data
return 0;
}
/*
Return dimension for object
SYNOPSIS
dimension()
res_dim Result dimension
end End of object will be stored here. May be 0 for
simple objects!
RETURN
0 ok
1 error
*/
bool Gis_geometry_collection::dimension(uint32 *res_dim,
wkb_parser *wkb) const {
uint32 n_objects;
Geometry_buffer buffer;
Geometry *geom;
if (wkb->scan_non_zero_uint4(&n_objects)) return true;
*res_dim = 0;
while (n_objects--) {
uint32 dim;
if (!(geom = scan_header_and_create(wkb, &buffer)) ||
geom->dimension(&dim, wkb))
return true;
set_if_bigger(*res_dim, dim);
}
return false;
}
bool Gis_geometry_collection::reverse_coordinates() {
uint32 num_of_geometries;
size_t current_data_offset =
4; // Add num_of_geometries header size to offset.
String str(get_cptr(), get_nbytes(), &my_charset_bin);
if (num_geometries(&num_of_geometries)) {
return true;
}
for (uint32 i = 1; i <= num_of_geometries; i++) {
String result;
if (geometry_n(i, &result)) {
return true;
}
Geometry *g;
Geometry_buffer buffer;
if (!(g = Geometry::construct(&buffer, &result, false))) {
return true;
}
if (g->reverse_coordinates()) {
return true;
}
if (str.replace(current_data_offset, result.length(), result.ptr(),
result.length())) {
return true;
}
current_data_offset += result.length();
}
return false;
}
bool Gis_geometry_collection::validate_coordinate_range(
double srs_angular_unit, bool *long_out_of_range, bool *lat_out_of_range,
double *out_of_range_value) {
uint32 num_of_geometries;
*long_out_of_range = false;
*lat_out_of_range = false;
if (num_geometries(&num_of_geometries)) {
return true; /* purecov: inspected */
}
for (uint32 i = 1; i <= num_of_geometries; i++) {
String result;
if (geometry_n(i, &result)) {
return true; /* purecov: inspected */
}
Geometry *g;
Geometry_buffer buffer;
if (!(g = Geometry::construct(&buffer, &result, false))) {
return true; /* purecov: inspected */
}
if (g->validate_coordinate_range(srs_angular_unit, long_out_of_range,
lat_out_of_range, out_of_range_value)) {
return true;
}
}
return false;
}
const Geometry::Class_info *Gis_geometry_collection::get_class_info() const {
return &geometrycollection_class;
}
/************************* Stepper classes ****************************/
/**
Base class of all WKB parsers, which parse different types of geometries
properly. All these classes assume the WKB input is valid and complete.
*/
class Stepper_base {
public:
/**
Constructor.
@param dim dimension of points in the geometry to be stepped
over(i.e. current geometry).
@param bo current geometry's byte order
@param has_wkb_hdr true for stepping through geometries within multiXXX and
geometrycollection, false for other geometries.
@param geotype current geometry's type
*/
Stepper_base(char dim, Geometry::wkbByteOrder bo, bool has_wkb_hdr,
Geometry::wkbType geotype) {
m_dim = dim;
DBUG_ASSERT(bo == Geometry::wkb_ndr);
m_bo = bo;
m_has_wkb_hdr = has_wkb_hdr;
m_geotype = geotype;
}
Geometry::wkbByteOrder get_current_byte_order() const {
DBUG_ASSERT((m_bo == Geometry::wkb_xdr || m_bo == Geometry::wkb_ndr));
return m_bo;
}
Geometry::wkbType get_current_geotype() const {
DBUG_ASSERT(Geometry::is_valid_geotype(m_geotype));
return m_geotype;
}
protected:
/// Current geometry dimension.
char m_dim;
/// Current geometry has a WKB header or not.
bool m_has_wkb_hdr;
/// Current geometry's byte order.
Geometry::wkbByteOrder m_bo;
/// Current geometry's type, e.g. polygon, linestring, etc.
Geometry::wkbType m_geotype;
};
/**
For iterating points inside multipoint and linestring.
Expected multipoint format: NUM-pts|WKB-HDR1 pt1|WKB-HDR2 pt2|...|WKB-HDRn ptn
Expected linestring format doesn't have the WKB headers.
*/
class Point_stepper : public Stepper_base {
public:
Point_stepper(char dim, Geometry::wkbByteOrder bo, bool has_wkb_hdr)
: Stepper_base(dim, bo, has_wkb_hdr, Geometry::wkb_point) {}
const char *operator()(const char *p);
};
/**
For iterating linestrings inside multilinestring and polygon.
Expected multilinestring format: NUM-ls|WKB-HDR1 ls1|WKB-HDR2 ls2|....
Expected polygon format doesn't have the WKB headers, and the 1st one is
exterior ring, following if any are interior rings.
In both cases, the linestrX is of linestring format, with no WKB header
in its each point.
*/
class Linestring_stepper : public Stepper_base {
public:
Linestring_stepper(char dim, Geometry::wkbByteOrder bo, bool has_wkb_hdr)
: Stepper_base(dim, bo, has_wkb_hdr, Geometry::wkb_linestring) {}
const char *operator()(const char *p);
};
/**
For iterating polygons inside multipolygon or geometry collection.
Expected multipolygon format: NUM-plgns|WKB-HDR1 plgn1|WKB-HDR2 plgn2|...
This is also expected format for geometry collection.
In both cases inside polygonX there is no more WKB headers.
*/
class Polygon_stepper : public Stepper_base {
public:
Polygon_stepper(char dim, Geometry::wkbByteOrder bo, bool has_wkb_hdr)
: Stepper_base(dim, bo, has_wkb_hdr, Geometry::wkb_polygon) {}
const char *operator()(const char *p);
};
/// Parsing operator. Note that the returned pointer may point past end of
/// WKB string, and caller is responsible for stoping reading after last
/// geometry is read, this is true for all parsing operator of all stepper
/// classes.
/// @param p points to the 1st byte of a point's wkb data, right after its
/// wkb header if any; returns the next point's wkb data's 1st byte pointer,
/// skipping its wkb header if any.
const char *Point_stepper::operator()(const char *p) {
p += SIZEOF_STORED_DOUBLE * m_dim;
// m_bo is latest byte order, which allows mixed byte orders in the same
// wkb byte string.
if (m_has_wkb_hdr) {
Geometry::wkbByteOrder bo = get_byte_order(p);
// The next one can be other geo types, in a geometry collection.
m_geotype = get_wkb_geotype(p + 1);
if (m_bo != bo) m_bo = bo;
p += WKB_HEADER_SIZE;
}
return p;
}
/// Parsing operator.
/// @param p points to the 1st byte of a linestring's wkb data, right after
/// its wkb header if any;
/// @return the next linestring's wkb data's 1st
/// byte pointer, skipping its wkb header if any.
const char *Linestring_stepper::operator()(const char *p) {
uint32 npts = 0;
npts = uint4korr(p);
p += sizeof(uint32);
p += npts * SIZEOF_STORED_DOUBLE * m_dim;
// The m_bo is latest byte order, which allows mixed byte orders in the same
// wkb byte string.
if (m_has_wkb_hdr) {
Geometry::wkbByteOrder bo = get_byte_order(p);
// The next one can be other geo types, in a geometry collection.
m_geotype = get_wkb_geotype(p + 1);
if (m_bo != bo) m_bo = bo;
p += WKB_HEADER_SIZE; // skip the wkb header if any
}
return p;
}
/// Parsing operator.
/// @param p points to the 1st byte of a polygon's wkb data, right after its
/// wkb header if any;
/// @return the next polygon's wkb data's 1st byte pointer,
/// skipping its wkb header if any.
const char *Polygon_stepper::operator()(const char *p) {
uint32 nls = 0;
// We pass false because a multilinestring's points don't have
// wkb headers(5 bytes).
Linestring_stepper lsstepper(m_dim, m_bo, false);
nls = uint4korr(p);
p += sizeof(uint32);
for (uint32 i = 0; i < nls; i++) p = lsstepper(p);
// m_bo is latest byte order, which allows mixed byte orders in the same
// wkb byte string.
DBUG_ASSERT(m_has_wkb_hdr);
Geometry::wkbByteOrder bo = get_byte_order(p);
// The next one can be other geo types, in a geometry collection.
m_geotype = get_wkb_geotype(p + 1);
if (m_bo != bo) m_bo = bo;
p += WKB_HEADER_SIZE; // skip the wkb header if any
return p;
}
/**
Get inner rings object from a geometry. Internally check that the argument
is a polygon. This function is intended as a helper function and is called
where we don't convert to a polygon pointer although it is a polygon.
@param g a geometry that must be a polygon.
@return the polygon's inner rings object.
*/
// SUPPRESS_UBSAN Wrong downcast. FIXME
static inline Gis_polygon::inner_container_type *inner_rings(const Geometry *g)
SUPPRESS_UBSAN;
static inline Gis_polygon::inner_container_type *inner_rings(
const Geometry *g) {
DBUG_ASSERT(g->get_geotype() == Geometry::wkb_polygon);
const Gis_polygon *p = static_cast<const Gis_polygon *>(g);
return p->inner_rings();
}
/**
Set inner rings object to a geometry. Internally check that the argument
is a polygon. This function is intended as a helper function and is called
where we don't convert to a polygon pointer although it is a polygon.
@param g a geometry that must be a polygon.
@param inns
@return the polygon's inner rings object.
*/
// SUPPRESS_UBSAN Wrong downcast. FIXME
static inline void set_inner_rings(
Geometry *g, Gis_polygon::inner_container_type *inns) SUPPRESS_UBSAN;
static inline void set_inner_rings(Geometry *g,
Gis_polygon::inner_container_type *inns) {
DBUG_ASSERT(g->get_geotype() == Geometry::wkb_polygon);
Gis_polygon *p = static_cast<Gis_polygon *>(g);
p->set_inner_rings(inns);
}
/// Parse the wkb buffer to build the component vector m_geo_vect for geom.
/// Set each geometry's wkb pointer into the Geometry objects inside m_geo_vect.
/// Make it a standalone function in order to be able to access classes defined
/// after class template Gis_wkb_vector.
/// @param geom the geometry to analyze and parse.
/// @param p points to the geometry's wkb data's 1st byte, right after its
/// wkb header if any.
/// @param num_geoms number of following geometries, to be used only when
/// parsing the WKB of a polygon's inner rings because there is no WKB header
/// for the inner rings only.
void parse_wkb_data(Geometry *geom, const char *p, size_t num_geoms) {
const char *q = NULL;
size_t nbytes = 0;
Geometry::wkbType geotype = geom->get_geotype();
Geometry::wkbByteOrder mybo = geom->get_byte_order();
char dim = geom->get_dimension();
DBUG_ASSERT(geotype != Geometry::wkb_polygon_inner_rings ||
(geotype == Geometry::wkb_polygon_inner_rings && num_geoms != 0));
geom->set_bg_adapter(true);
if (p == NULL) return;
switch (geotype) {
case Geometry::wkb_point:
// Point doesn't need this vector.
DBUG_ASSERT(false);
break;
case Geometry::wkb_linestring: {
uint32 npts = uint4korr(p);
p += sizeof(uint32);
Point_stepper ptstep(dim, mybo, false);
const char *first = NULL, *last = NULL;
for (uint32 i = 0; i < npts; i++) {
q = p;
if (i == 0) first = p;
if (i < npts - 1) {
p = ptstep(p);
nbytes = p - q;
} else {
nbytes = geom->get_cptr() + geom->get_nbytes() - p;
last = p;
}
DBUG_ASSERT(nbytes == dim * SIZEOF_STORED_DOUBLE);
// Construct the geometry object as below to avoid unncesarrily
// parsing its WKB data. Parsing will be done in shallow_copy.
Gis_point ent;
ent.Geometry::set_ptr(q);
ent.set_nbytes(nbytes);
ent.set_owner(geom);
geom->shallow_push(&ent);
}
/*
Historically MySQL GIS require closed polygon rings from user input,
and from now (5.7.4) on we close a polygon ring if it's open before
storing/using it. So we should never see any open rings here.
However we can't compare memory because the two points may have tiny
differences due to computing deviations.
*/
// Fix compiler warnings.
first = last;
last = first;
break;
}
case Geometry::wkb_multipoint: {
uint32 npts = uint4korr(p);
p += sizeof(uint32);
Geometry::wkbByteOrder bo = ::get_byte_order(p);
DBUG_ASSERT(get_wkb_geotype(p + 1) == Geometry::wkb_point);
p += WKB_HEADER_SIZE;
Point_stepper ptstep(dim, bo, true);
for (uint32 i = 0; i < npts; i++) {
q = p;
if (i < npts - 1) {
p = ptstep(p);
nbytes = p - q - WKB_HEADER_SIZE;
} else
nbytes = geom->get_cptr() + geom->get_nbytes() - p;
// Construct the geometry object as below to avoid unncesarrily
// parsing its WKB data. Parsing will be done in shallow_copy.
Gis_point ent;
ent.Geometry::set_ptr(q);
ent.set_nbytes(nbytes);
ent.set_owner(geom);
geom->shallow_push(&ent);
bo = ptstep.get_current_byte_order();
DBUG_ASSERT(ptstep.get_current_geotype() == Geometry::wkb_point);
}
break;
}
case Geometry::wkb_multilinestring: {
uint32 nls = uint4korr(p);
p += sizeof(uint32);
Geometry::wkbByteOrder bo = ::get_byte_order(p);
DBUG_ASSERT(get_wkb_geotype(p + 1) == Geometry::wkb_linestring);
p += WKB_HEADER_SIZE;
Linestring_stepper lsstep(dim, bo, true);
for (uint32 i = 0; i < nls; i++) {
q = p;
if (i < nls - 1) {
p = lsstep(p);
nbytes = p - q - WKB_HEADER_SIZE;
} else
nbytes = geom->get_cptr() + geom->get_nbytes() - p;
// Construct the geometry object as below to avoid unncesarrily
// parsing its WKB data. Parsing will be done in shallow_copy.
Gis_line_string ent;
ent.Geometry::set_ptr(q);
ent.set_nbytes(nbytes);
ent.set_owner(geom);
geom->shallow_push(&ent);
bo = lsstep.get_current_byte_order();
DBUG_ASSERT(lsstep.get_current_geotype() == Geometry::wkb_linestring);
}
break;
}
case Geometry::wkb_polygon_inner_rings: {
/*
There is no independent WKT for inner rings to parse. Inner rings is
a component of a polygon thus can't have number of rings in its WKB.
When inner rings are parsed here, it must already have been filled,
and we are calling this in methods like push_back, etc,
thus m_geo_vect has the right number of rings.
*/
size_t nls = num_geoms;
Linestring_stepper lsstep(dim, mybo, false);
for (size_t i = 0; i < nls; i++) {
q = p;
if (i < nls - 1) {
p = lsstep(p);
nbytes = p - q;
} else
nbytes = geom->get_cptr() + geom->get_nbytes() - p;
// Construct the geometry object as below to avoid unncesarrily
// parsing its WKB data. Parsing will be done in shallow_copy.
Gis_polygon_ring ent;
ent.Geometry::set_ptr(q);
ent.set_nbytes(nbytes);
ent.set_owner(geom);
geom->shallow_push(&ent);
}
break;
}
case Geometry::wkb_polygon: {
uint32 nls = uint4korr(p);
const char *start = p;
p += sizeof(uint32);
Linestring_stepper lsstep(dim, mybo, false);
for (uint32 i = 0; i < nls; i++) {
q = p;
if (i < nls - 1) {
p = lsstep(p);
nbytes = p - q;
} else
nbytes = start + geom->get_nbytes() - p;
if (i == 0) {
// Parse outer ring.
Gis_polygon_ring *outer = outer_ring(geom);
if (geom->get_ptr() == NULL) {
outer = new Gis_polygon_ring(
q, nbytes, Geometry::Flags_t(Geometry::wkb_linestring, 0),
geom->get_srid());
outer->set_props(Geometry::POLYGON_OUTER_RING);
geom->Geometry::set_ptr(outer);
} else {
outer->set_ptr(const_cast<char *>(q), nbytes);
outer->set_byte_order(mybo);
outer->set_dimension(dim);
outer->set_geotype(Geometry::wkb_linestring);
}
outer->set_owner(geom);
/*
When parsing data to get polygon, the memory is a continuous chunk
belonging to the owner, or it's an existing WKB buffer. Also true
for below inner rings.
*/
outer->set_ownmem(false);
} else {
Gis_polygon::inner_container_type *inners = inner_rings(geom);
if (inners == NULL) {
inners = new Gis_polygon::inner_container_type();
set_inner_rings(geom, inners);
inners->set_byte_order(mybo);
inners->set_dimension(dim);
inners->set_geotype(Geometry::wkb_polygon_inner_rings);
inners->set_owner(geom);
inners->set_geo_vect(new Geometry_vector<Gis_polygon_ring>());
inners->set_ptr(const_cast<char *>(q), 0 /* Accumulated below. */);
inners->set_ownmem(false);
}
// Construct the geometry object as below to avoid unncesarrily
// parsing its WKB data. Parsing will be done in shallow_push.
Gis_polygon_ring ent;
ent.Geometry::set_ptr(q);
ent.set_nbytes(nbytes);
ent.set_owner(inners);
ent.set_props(Geometry::POLYGON_INNER_RING);
inners->shallow_push(&ent);
inners->set_nbytes(inners->get_nbytes() + nbytes);
}
}
geom->polygon_is_wkb_form(false);
break;
}
case Geometry::wkb_multipolygon: {
uint32 nplgns = uint4korr(p);
p += sizeof(uint32);
Geometry::wkbByteOrder bo = ::get_byte_order(p);
DBUG_ASSERT(get_wkb_geotype(p + 1) == Geometry::wkb_polygon);
p += WKB_HEADER_SIZE;
Polygon_stepper plgn_step(dim, bo, true);
for (uint32 i = 0; i < nplgns; i++) {
q = p;
if (i < nplgns - 1) {
p = plgn_step(p);
nbytes = p - q - WKB_HEADER_SIZE;
} else
nbytes = geom->get_cptr() + geom->get_nbytes() - p;
/*
Construct the geometry object as below to avoid unncesarrily
parsing its WKB data. Parsing will be done in shallow_copy.
*/
Gis_polygon ent;
ent.Geometry::set_ptr(q);
ent.set_nbytes(nbytes);
ent.set_owner(geom);
geom->shallow_push(&ent);
// The object 'ent' doesn't have any data of its own.
ent.donate_data();
bo = plgn_step.get_current_byte_order();
DBUG_ASSERT(plgn_step.get_current_geotype() == Geometry::wkb_polygon);
}
break;
}
case Geometry::wkb_geometrycollection:
/*
We never create a Gis_wkb_vector using a geometry collection, because
BG never uses such a type.
*/
DBUG_ASSERT(false);
break;
default:
DBUG_ASSERT(false);
break;
}
}
/**
In place normalize polygons' rings, making outer ring CCW and inner rings CW
by reversing the ring's points in the WKB buffer inplace. This function can
not be made a virtual function since BG adapter geometry objects may also
need it.
@return the WKB buffer address of the geometry which contains the
converted WKB data. If geometry data is invalid, returns NULL.
*/
const void *Geometry::normalize_ring_order() {
Geometry *geo = this;
bool inval = false;
if (geo->get_type() == Geometry::wkb_polygon) {
Gis_polygon bgeo(geo->get_data_ptr(), geo->get_data_size(),
geo->get_flags(), geo->get_srid());
if (bgeo.set_polygon_ring_order()) inval = true;
} else if (geo->get_type() == Geometry::wkb_multipolygon) {
Gis_multi_polygon bgeo(geo->get_data_ptr(), geo->get_data_size(),
geo->get_flags(), geo->get_srid());
for (size_t i = 0; i < bgeo.size(); i++)
if (bgeo[i].set_polygon_ring_order()) {
inval = true;
break;
}
} else if (geo->get_type() == Geometry::wkb_geometrycollection) {
/*
This is impossible because BG doesn't use a geometry collection, and
we can't create a Gis_wkb_vector<T> with a geometry collection.
*/
DBUG_ASSERT(false);
}
if (inval) return NULL;
return geo->get_data_ptr();
}
/**
Because of resize, a geometry's components may reside not in one chunk,
some may in the m_ptr's chunk; others have their own memory and only exist
in m_geo_vect vector, not in ptr's chunk. Also, a constructed polygon's
data is always not in a chunk and needs to be so when it's pushed into a
multipolygon/geometry collection.
Thus in mysql before using the returned geometry, also inside the
container classes before using the wkb data or clearing m_geo_vect,
we need to make them inline, i.e. reside in one chunk of memory.
Can only resize a topmost geometry, thus no recursive reassemling
to do for now.
Algorithm:
Step 1. Structure analysis
Scan this geometry's components, see whether each of them has its own
memory, if so it's 'out of line', otherwise it's 'inline'. Note down
those owning memory in a map M1, for each entry X in the map M1, the
component's index in the component vector m_geo_vect is used as key;
The inline chunk of memory right before it which may have any number
of inline components, and the inline chunk's start and end address pair
is used as value of the inserted item X. If there is no inline chunk
before the component, X's pointer range is (0, 0). The inline chunk's
starting address is well maintained during the scan.
Step 2. Reassembling
Allocate enough memory space (the length is accumulated in step 1) as WKB
buffer and call it GBuf here, then copy the WKB of inline and out-of-line
geometries into GBuf in original order:
Go through the map by index order, for each item, copy the WKB chunk
before it into the WKB buffer, then copy this out-of-line geometry's WKB
into GBuf.
Special treatment of polygon: we have to pack its value and store their
WKB separately into a map GP in step 1, and in step 2 for a polygon,
get its WKB from GP, and at the end release WKB memory buffers held by
items of GP.
*/
template <typename T>
void Gis_wkb_vector<T>::reassemble() {
set_bg_adapter(true);
Geometry::wkbType geotype = get_geotype();
if (geotype == Geometry::wkb_point || geotype == Geometry::wkb_polygon ||
geotype == Geometry::wkb_multipoint || m_geo_vect == NULL ||
geotype == Geometry::wkb_linestring || m_geo_vect->size() == 0 ||
!has_out_of_line_components())
return;
if (m_geo_vect == NULL) m_geo_vect = new Geo_vector;
typedef std::map<size_t, std::pair<const char *, const char *>> segs_t;
segs_t segs;
size_t hdrsz = 0, num = m_geo_vect->size(), prev_in = 0, totlen = 0,
segsz = 0;
Geo_vector &vec = *m_geo_vect;
const char *start = get_cptr(), *end = NULL, *prev_start = get_cptr();
std::map<size_t, std::pair<void *, size_t>> plgn_data;
std::map<size_t, std::pair<void *, size_t>>::iterator plgn_data_itr;
bool is_inns = (geotype == Geometry::wkb_polygon_inner_rings);
// True if just passed by a geometry having its own memory and not stored
// inside owner's memory during the scan.
bool out = false;
if (geotype != Geometry::wkb_polygon_inner_rings) hdrsz = WKB_HEADER_SIZE;
uint32 none = 0; // Used when all components are out of line.
// Starting step one of the algorithm --- Structure Analysis.
for (size_t i = 0; i < num; i++) {
T *veci = &(vec[i]);
// Polygons are always(almost) out of line. One with its own memory is
// always out of line.
if (veci->get_geotype() == Geometry::wkb_polygon || veci->get_ownmem()) {
// In case of a polygon, see if it's already inline in a different
// way from other types of geometries.
if (veci->get_geotype() == Geometry::wkb_polygon &&
polygon_is_packed(veci, this)) {
if (out) {
out = false;
DBUG_ASSERT(prev_start == veci->get_ptr());
}
prev_in = i;
continue;
}
// Record the bytes before 1st geometry component.
if (i == 0) {
if (m_ptr) {
start = get_cptr();
end = start + sizeof(uint32) /* num geometrys*/;
} else if (!is_inns) {
start = reinterpret_cast<char *>(&none);
end = start + sizeof(none);
} else
start = end = NULL;
}
// The previous geometry is already out of line, or no m_ptr allocated.
else if (out || !prev_start) {
start = NULL;
end = NULL;
} else // The previous geometry is inline, note down the inline range.
{
start = prev_start;
if (veci->get_geotype() == Geometry::wkb_polygon)
end = get_packed_ptr(&(vec[prev_in])) + vec[prev_in].get_nbytes();
else
end = vec[prev_in].get_cptr() + vec[prev_in].get_nbytes();
prev_start = end;
// The 'end' points to the 1st byte of next geometry stored in its
// owner's memory.
}
if (veci->get_geotype() != Geometry::wkb_polygon) {
// When this geometry is a geometry collection, we need to make its
// components in one chunk first. Not gonna implement this yet since
// BG doesn't use geometry collection yet, and consequently no
// component can be a multipoint/multilinestring/multipolygon or a
// geometrycollection. And multipoint components are already supported
// so not forbidding them here.
#if !defined(DBUG_OFF)
Geometry::wkbType veci_gt = veci->get_geotype();
#endif
DBUG_ASSERT(veci_gt != wkb_geometrycollection &&
veci_gt != wkb_multilinestring &&
veci_gt != wkb_multipolygon);
/* A point/multipoint/linestring is always in one memory chunk. */
totlen += veci->get_nbytes() + hdrsz;
} else {
// Must be a polygon out of line.
size_t nbytes = 0;
void *plgn_base = get_packed_ptr(veci, &nbytes);
DBUG_ASSERT(veci->get_nbytes() == 0 || veci->get_nbytes() == nbytes);
veci->set_nbytes(nbytes);
plgn_data.insert(std::make_pair(i, std::make_pair(plgn_base, nbytes)));
totlen += nbytes + hdrsz;
}
segs.insert(std::make_pair(i, std::make_pair(start, end)));
out = true;
} else {
if (out) {
out = false;
DBUG_ASSERT(prev_start == veci->get_ptr());
}
prev_in = i;
}
}
segsz = segs.size();
if (segsz == 0) {
has_out_of_line_components(false);
return;
}
size_t nbytes = get_nbytes();
DBUG_ASSERT((nbytes == 0 && m_ptr == NULL && num == segsz) ||
(nbytes > 0 && num >= segsz));
// If all are out of line, m_ptr is 0 and no room for ring count, otherwise
// the space for ring count is already counted above.
totlen += (nbytes ? nbytes : (is_inns ? 0 : sizeof(uint32)));
size_t len = 0, total_len = 0, last_i = 0, numgeoms = 0;
// Allocate extra space as free space for the WKB buffer, and write it as
// defined pattern.
const size_t extra_wkb_free_space = 32;
char *ptr = static_cast<char *>(gis_wkb_alloc(totlen + extra_wkb_free_space));
// The header(object count) is already copied.
char *q = ptr;
if (ptr == NULL) {
clear_wkb_data();
m_ptr = NULL;
set_nbytes(0);
set_ownmem(false);
goto exit;
}
memset(ptr + totlen, 0xff, extra_wkb_free_space - 1);
ptr[totlen + extra_wkb_free_space - 1] = '\0';
// Starting step two of the algorithm --- Reassembling.
// Assemble the ins and outs into a single chunk.
for (segs_t::iterator itr = segs.begin(); itr != segs.end(); ++itr) {
size_t i = itr->first;
start = itr->second.first;
end = itr->second.second;
const Geometry *veci = &(vec[i]);
last_i = i;
// Copy the inline geometries before veci into buffer.
if (start) {
memcpy(q, start, len = end - start);
q += len;
total_len += len;
}
// Set WKB header. This geometry must be one of multilinestring,
// multipolygon or a polygon's inner rings.
if (get_geotype() != Geometry::wkb_polygon_inner_rings) {
q = write_wkb_header(q, veci->get_geotype());
total_len += hdrsz;
}
// Copy the out of line geometry into buffer. A polygon's data isn't
// packed inside itself, we've packed it and recorded it in plgn_data.
plgn_data_itr = plgn_data.find(i);
if (veci->get_geotype() != Geometry::wkb_polygon) {
DBUG_ASSERT(plgn_data_itr == plgn_data.end());
len = veci->get_nbytes();
memcpy(q, veci->get_ptr(), len);
} else {
DBUG_ASSERT(plgn_data_itr != plgn_data.end());
len = plgn_data_itr->second.second;
memcpy(q, plgn_data_itr->second.first, len);
}
q += len;
total_len += len;
}
// There may be trailing inline geometries to copy at old tail.
if (last_i < vec.size() - 1) {
len = get_cptr() + get_nbytes() - prev_start;
memcpy(q, prev_start, len);
total_len += len;
}
DBUG_ASSERT(total_len == totlen);
// Inner rings doesn't have ring count.
if (!is_inns) {
DBUG_ASSERT(segsz + uint4korr(ptr) <= 0xFFFFFFFF);
int4store(reinterpret_cast<uchar *>(ptr),
uint4korr(ptr) + static_cast<uint32>(segsz));
}
numgeoms = m_geo_vect->size();
clear_wkb_data();
set_ptr(ptr, totlen);
// An inner ring isn't parsed in set_ptr, has to parse separately since
// we don't know its number of rings.
if (is_inns) parse_wkb_data(this, get_cptr(), numgeoms);
set_ownmem(true);
exit:
for (plgn_data_itr = plgn_data.begin(); plgn_data_itr != plgn_data.end();
++plgn_data_itr)
gis_wkb_free(plgn_data_itr->second.first);
has_out_of_line_components(false);
}
/// @brief Constructor.
/// @param ptr points to the geometry's wkb data's 1st byte, right after its
/// wkb header if any.
/// @param nbytes the byte order indicated by ptr's wkb header.
/// @param flags
/// @param srid
/// @param is_bg_adapter Whether this object is created to be used by
/// Boost Geometry, or to be only used in MySQL code.
template <typename T>
Gis_wkb_vector<T>::Gis_wkb_vector(const void *ptr, size_t nbytes,
const Flags_t &flags, gis::srid_t srid,
bool is_bg_adapter)
: Geometry(ptr, nbytes, flags, srid) {
DBUG_ASSERT((ptr != NULL && nbytes > 0) || (ptr == NULL && nbytes == 0));
set_ownmem(false); // We use existing WKB data and don't own that memory.
set_bg_adapter(is_bg_adapter);
m_geo_vect = NULL;
if (!is_bg_adapter) return;
std::unique_ptr<Geo_vector> guard;
wkbType geotype = get_geotype();
// Points don't need it, polygon creates it when parsing.
if (geotype != Geometry::wkb_point && geotype != Geometry::wkb_polygon &&
ptr != NULL)
guard.reset(m_geo_vect = new Geo_vector());
// For polygon parsing to work
if (geotype == Geometry::wkb_polygon) m_ptr = NULL;
// Why: wkb_polygon_inner_rings should parse in polygon as a whole.
// Don't call get_cptr() here, it returns NULL.
if (geotype != Geometry::wkb_polygon_inner_rings && ptr != NULL)
parse_wkb_data(this, static_cast<const char *>(ptr));
guard.release();
}
template <typename T>
Gis_wkb_vector<T>::Gis_wkb_vector(const Gis_wkb_vector<T> &v)
: Geometry(v), m_geo_vect(NULL) {
DBUG_ASSERT((v.get_ptr() != NULL && v.get_nbytes() > 0) ||
(v.get_ptr() == NULL && !v.get_ownmem() && v.get_nbytes() == 0));
if (!v.is_bg_adapter() || (v.get_ptr() == NULL && v.m_geo_vect == NULL))
return;
m_geo_vect = new Geo_vector();
std::unique_ptr<Geo_vector> guard(m_geo_vect);
const_cast<self &>(v).reassemble();
set_flags(v.get_flags());
set_nbytes(v.get_nbytes());
if (get_nbytes() > 0) {
m_ptr = gis_wkb_alloc(v.get_nbytes() + 2);
if (m_ptr == NULL) {
m_geo_vect = NULL;
set_ownmem(false);
set_nbytes(0);
return;
}
memcpy(m_ptr, v.get_ptr(), v.get_nbytes());
/*
The extra 2 bytes makes the buffer usable by get_nbytes_free.
It's hard to know how many more space will be needed so let's
allocate more later.
*/
get_cptr()[get_nbytes()] = '\xff';
get_cptr()[get_nbytes() + 1] = '\0';
parse_wkb_data(this, get_cptr(), v.get_geo_vect()->size());
set_ownmem(true);
}
guard.release();
}
/**
Deep assignment from vector 'rhs' to this object.
@param rhs the Gis_wkb_vector<T> instance to duplicate from.
*/
template <typename T>
Gis_wkb_vector<T> &Gis_wkb_vector<T>::operator=(const Gis_wkb_vector<T> &rhs) {
if (this == &rhs) return *this;
Geometry::operator=(rhs);
DBUG_ASSERT((m_ptr != NULL && get_ownmem() && get_nbytes() > 0) ||
(m_ptr == NULL && !get_ownmem() && get_nbytes() == 0));
DBUG_ASSERT(
(rhs.get_ptr() != NULL && rhs.get_nbytes() > 0) ||
(rhs.get_ptr() == NULL && !rhs.get_ownmem() && rhs.get_nbytes() == 0));
if (m_owner == NULL) m_owner = rhs.get_owner();
size_t nbytes_free = get_nbytes_free();
clear_wkb_data();
if (rhs.get_ptr() == NULL) {
if (m_ptr != NULL) gis_wkb_free(m_ptr);
m_ptr = NULL;
set_flags(rhs.get_flags());
return *this;
}
/*
Geometry v may have out of line components, need to reassemble first.
*/
const_cast<self &>(rhs).reassemble();
/*
If have no enough space, reallocate with extra space padded with required
bytes;
*/
if (m_ptr == NULL || get_nbytes() + nbytes_free < rhs.get_nbytes()) {
gis_wkb_free(m_ptr);
m_ptr = gis_wkb_alloc(rhs.get_nbytes() + 32 /* some extra space. */);
if (m_ptr == NULL) {
/*
This object in this case is valid although it doesn't have any data.
*/
set_nbytes(0);
set_ownmem(false);
return *this;
}
// Fill extra space with pattern defined by
// Gis_wkb_vector<>::get_nbytes_free().
char *cp = get_cptr();
memset(cp + rhs.get_nbytes(), 0xFF, 32);
cp[rhs.get_nbytes() + 31] = '\0';
}
/*
If need less space than before, set remaining bytes to 0xFF as requred
by Gis_wkb_vector<>::get_nbytes_free.
*/
if (get_nbytes() > rhs.get_nbytes())
memset(get_cptr() + rhs.get_nbytes(), 0xFF,
get_nbytes() - rhs.get_nbytes());
memcpy(m_ptr, rhs.get_ptr(), rhs.get_nbytes());
set_flags(rhs.get_flags());
set_ownmem(true);
m_geo_vect = new Geo_vector();
parse_wkb_data(this, get_cptr());
return *this;
}
/**
The copy constructors of Geometry classes always do deep copy, but when
pushing a Geometry object into its owner's geo.m_geo_vect, we want to do
shallow copy because we want all elements in geo.m_geo_vect vector point
into locations in the geo.m_ptr buffer. In such situations call this
function.
@param g The Geometry object to push into vec.
@return The address of the Geometry object stored in vec.
*/
template <typename T>
void Gis_wkb_vector<T>::shallow_push(const Geometry *g) {
const T &geo = *(static_cast<const T *>(g));
T *pgeo = NULL;
if (m_geo_vect == NULL) m_geo_vect = new Geo_vector();
// Allocate space and create an object with its default constructor.
pgeo = static_cast<T *>(m_geo_vect->append_object());
DBUG_ASSERT(pgeo != NULL);
if (pgeo == NULL) return;
pgeo->set_flags(geo.get_flags());
pgeo->set_srid(geo.get_srid());
pgeo->set_bg_adapter(true);
// Such a shallow copied object never has its own memory regardless of geo.
pgeo->set_ownmem(false);
// This will parse and set up pgeo->m_geo_vect properly.
// Do not copy elements from geo.m_geo_vect into that of pgeo
// otherwise STL does deep copy using the Geometry copy constructor.
pgeo->set_ptr(geo.get_ptr(), geo.get_nbytes());
pgeo->set_owner(geo.get_owner());
}
template <typename T>
void Gis_wkb_vector<T>::set_ptr(void *ptr, size_t len) {
DBUG_ASSERT(!(ptr == NULL && len > 0));
set_bg_adapter(true);
if (get_geotype() != Geometry::wkb_polygon) {
if (get_ownmem() && m_ptr != NULL) gis_wkb_free(m_ptr);
m_ptr = ptr;
if (m_geo_vect) clear_wkb_data();
}
set_nbytes(len);
/* When invoked, this object may or may not have its own memory. */
if (get_geotype() != Geometry::wkb_polygon_inner_rings && m_ptr != NULL) {
if (m_geo_vect == NULL) m_geo_vect = new Geo_vector();
parse_wkb_data(this, get_cptr());
}
}
/**
Update support
We suppose updating a geometry can happen in the following ways:
1. create an empty geo, then append components into it, the geo must
be a topmost one; a complex geometry such as a multilinestring can be
seen as a tree of geometry components, and the mlstr is the topmost
geometry, i.e. the root of the tree, its lstrs are next layer of nodes,
their points are the 3rd layer of tree nodes. Only the root owns the
wkb buffer, other components point somewhere into the buffer, and can
only read the data.
Polygons are only used by getting its exterior ring or inner rings and
then work on that/those rings, never used as a whole.
2. *itr=value, each geo::m_owner can be used to track the topmost
memory owner, and do reallocation to accormodate the value. This is
for now not supported, will be if needed.
So far geometry assignment are only used for point objects in boost
geometry, thus only Geometry and Gis_point have operator=, no other
classes need so, and thus there is no need for reallocation.
3. call resize() to append some objects at the end, then assign/append
values to the added objects using push_back. Objects added this way
are out of line(unless the object is a point), and user need to call
reassemble() to make them inline, i.e. stored in its owner's memory.
*/
/// Clear geometry data of this object.
template <typename T>
void Gis_wkb_vector<T>::clear() {
if (!m_geo_vect) {
DBUG_ASSERT(m_ptr == NULL);
return;
}
DBUG_ASSERT(m_geo_vect && get_geotype() != Geometry::wkb_polygon);
// Keep the component vector because this object can be reused again.
const void *ptr = get_ptr();
set_bg_adapter(true);
if (ptr && get_ownmem()) {
gis_wkb_free(const_cast<void *>(ptr));
set_ownmem(false);
}
m_ptr = NULL;
clear_wkb_data();
set_nbytes(0);
}
/// Returns payload number of bytes of the topmost geometry holding this
/// geometry, i.e. the memory owner.
template <typename T>
size_t Gis_wkb_vector<T>::current_size() const {
// Polygon's data may not stay in a continuous chunk, and we update
// its data using the outer/inner rings.
DBUG_ASSERT(get_geotype() != Geometry::wkb_polygon);
set_bg_adapter(true);
if (m_geo_vect == NULL || m_geo_vect->empty()) return 0;
return get_nbytes();
}
/// Get number of free bytes in the buffer held by m_ptr. this object must be
/// an topmost geometry which owns memory.
template <typename T>
size_t Gis_wkb_vector<T>::get_nbytes_free() const {
DBUG_ASSERT((this->get_ownmem() && m_ptr) || (!get_ownmem() && !m_ptr));
size_t cap = current_size();
if (cap == 0) {
DBUG_ASSERT(m_ptr == NULL);
return 0;
}
const char *p = NULL, *ptr = get_cptr();
DBUG_ASSERT(ptr != NULL);
/*
There will always be remaining free space because in push_back, when
number of free bytes equals needed bytes we will do a realloc.
*/
for (p = ptr + cap; *p != 0; p++)
;
return p - ptr - cap + 1;
}
template <typename T>
void Gis_wkb_vector<T>::push_back(const T &val) {
Geometry::wkbType geotype = get_geotype();
DBUG_ASSERT(geotype != Geometry::wkb_polygon &&
((m_ptr && get_ownmem()) || (!m_ptr && !get_ownmem())));
// Only three possible types of geometries for val, thus no need to
// do val.reassemble().
DBUG_ASSERT(val.get_geotype() == wkb_point ||
val.get_geotype() == wkb_polygon ||
val.get_geotype() == wkb_linestring);
DBUG_ASSERT(val.get_ptr() != NULL);
size_t cap = 0, nalloc = 0;
size_t vallen, needed;
void *src_val = val.get_ptr();
if (m_geo_vect == NULL) m_geo_vect = new Geo_vector;
set_bg_adapter(true);
vallen = val.get_nbytes();
/*
Often inside bg, a polygon is created with no data, then append points
into outer ring and inner rings, such a polygon is a 'constructed'
polygon, and in this case we need to assemble
its data into a continuous chunk.
*/
if (val.get_geotype() == Geometry::wkb_polygon)
src_val = get_packed_ptr(&val, &vallen);
// The 4 types can be resized and have out-of-line components,
// reassemble first in case we lose them when doing m_geo_vect->clear().
if (geotype == Geometry::wkb_multilinestring ||
geotype == Geometry::wkb_geometrycollection ||
geotype == Geometry::wkb_polygon_inner_rings ||
geotype == Geometry::wkb_multipolygon)
reassemble();
// Get cap only after reassemble().
cap = current_size();
needed = vallen + WKB_HEADER_SIZE;
// Use >= instead of > because we always want to have trailing free bytes.
if (needed >= this->get_nbytes_free()) {
nalloc = cap + ((needed * 2 > 256) ? needed * 2 : 256);
void *ptr = get_ptr();
m_ptr = gis_wkb_realloc(m_ptr, nalloc);
if (m_ptr == NULL) {
set_nbytes(0);
set_ownmem(0);
clear_wkb_data();
return;
}
// Set unused space to -1, and last unused byte to 0.
// Function get_nbytes_free relies on this format.
memset(get_cptr() + cap, 0xff, nalloc - cap);
get_cptr()[nalloc - 1] = '\0';
memset(get_cptr() + cap, 0, sizeof(uint32));
bool replaced = (ptr != m_ptr);
set_ownmem(true);
if (m_owner && m_owner->get_geotype() == Geometry::wkb_polygon)
m_owner->set_ownmem(true);
// After reallocation we need to parse again.
if (cap > 0 && replaced) {
size_t ngeos = 0;
if (geotype == Geometry::wkb_polygon_inner_rings) ngeos = size();
clear_wkb_data();
parse_wkb_data(this, get_cptr(), ngeos);
}
}
size_t wkb_header_size = 0;
/* Offset for obj count, if needed. */
size_t obj_count_len =
((cap == 0 && geotype != Geometry::wkb_polygon_inner_rings)
? sizeof(uint32)
: 0);
char *val_ptr = get_cptr() + cap + obj_count_len;
// Append WKB header first, if needed.
if (geotype == Geometry::wkb_multipoint ||
geotype == Geometry::wkb_multipolygon ||
geotype == Geometry::wkb_multilinestring ||
geotype == Geometry::wkb_geometrycollection) {
Geometry::wkbType vgt = val.get_geotype();
DBUG_ASSERT(
(geotype == Geometry::wkb_multipoint && vgt == Geometry::wkb_point) ||
(geotype == Geometry::wkb_multipolygon &&
vgt == Geometry::wkb_polygon) ||
(geotype == Geometry::wkb_multilinestring &&
vgt == Geometry::wkb_linestring) ||
geotype == Geometry::wkb_geometrycollection);
val_ptr = write_wkb_header(val_ptr, vgt);
wkb_header_size = WKB_HEADER_SIZE;
}
// Copy val's data into buffer, then parse it.
memcpy(val_ptr, src_val, vallen);
set_nbytes(get_nbytes() + wkb_header_size + obj_count_len + vallen);
// Append geometry component into m_geo_vect vector. Try to avoid
// unnecessary parse by calling the right version of set_ptr. And do
// shallow push so that the element in m_geo_vect point to WKB buffer
// rather than have its own copy of the same WKB data.
T val2;
val2.set_flags(val.get_flags());
val2.set_srid(val.get_srid());
val2.Geometry::set_ptr(val_ptr);
val2.set_nbytes(vallen);
val2.set_owner(this);
val2.set_ownmem(false);
shallow_push(&val2);
val2.Geometry::set_ptr(NULL);
if (val2.get_geotype() == Geometry::wkb_polygon)
own_rings(&(m_geo_vect->back()));
if (geotype != Geometry::wkb_polygon_inner_rings) {
int4store(get_ucptr(), uint4korr(get_ucptr()) + 1);
DBUG_ASSERT(uint4korr(get_ucptr()) == this->m_geo_vect->size());
}
if (val.get_geotype() == Geometry::wkb_polygon) gis_wkb_free(src_val);
}
/*
Resize as in std::vector<>::resize().
Because resize can be called to append an empty geometry into its owner,
we have to allow pushing into an empty geo and its memory will not
be in the same chunk as its owner, which is OK for bg since the
Boost Range concept doesn't forbid so. But inside MySQL we should
reassemble the geometries into one chunk before using the WKB buffer
directly, by calling reassemble().
*/
template <typename T>
void Gis_wkb_vector<T>::resize(size_t sz) {
if (m_geo_vect == NULL) m_geo_vect = new Geo_vector;
Geometry::wkbType geotype = get_geotype();
size_t ngeo = m_geo_vect->size();
size_t dim = GEOM_DIM;
size_t ptsz = SIZEOF_STORED_DOUBLE * dim;
bool is_mpt = (geotype == Geometry::wkb_multipoint);
// Can resize a topmost geometry or a out of line geometry which has
// or will have its own memory(i.e. one that's not using others' memory).
// Points are fixed size, polygon doesn't hold data directly.
DBUG_ASSERT(!(m_ptr != NULL && !get_ownmem()) &&
geotype != Geometry::wkb_point &&
geotype != Geometry::wkb_polygon);
set_bg_adapter(true);
if (sz == ngeo) return;
// Shrinking the vector.
if (sz < ngeo) {
// Some elements may be out of line, must do so otherwise we don't
// know how much to shrink in m_ptr.
reassemble();
size_t sublen = 0;
for (size_t i = ngeo; i > sz; i--)
sublen += (*m_geo_vect)[i - 1].get_nbytes();
// '\0' not allowed in middle and no need for ending '\0' because it's
// at the end of the original free chunk which is right after this chunk.
memset((get_cptr() + get_nbytes() - sublen), 0xff, sublen);
set_nbytes(get_nbytes() - sublen);
#if !defined(DBUG_OFF)
bool rsz_ret = m_geo_vect->resize(sz);
DBUG_ASSERT(rsz_ret == false);
#else
m_geo_vect->resize(sz);
#endif
if (get_geotype() != Geometry::wkb_polygon_inner_rings) {
DBUG_ASSERT(uint4korr(get_ucptr()) == ngeo);
int4store(get_ucptr(), static_cast<uint32>(sz));
}
return;
}
char *ptr = NULL, *ptr2 = NULL;
// We can store points directly into its owner, points are fixed length,
// thus don't need its own memory.
if (geotype == Geometry::wkb_linestring ||
geotype == Geometry::wkb_multipoint) {
size_t left = get_nbytes_free(),
needed = (sz - ngeo) * (ptsz + (is_mpt ? WKB_HEADER_SIZE : 0)),
nalloc, cap = get_nbytes();
if (left <= needed) {
nalloc = cap + 32 * (left + needed);
ptr = get_cptr();
m_ptr = gis_wkb_realloc(m_ptr, nalloc);
if (m_ptr == NULL) {
set_nbytes(0);
set_ownmem(0);
clear_wkb_data();
return;
}
ptr2 = get_cptr();
memset((ptr2 + cap), 0xff, nalloc - cap);
ptr2[nalloc - 1] = '\0';
/*
Only set when cap is 0, otherwise after this call get_nbytes_free()
will work wrong, this is different from push_back because push_back
always put data here more than 4 bytes inside itself.
*/
if (cap == 0) int4store(get_ucptr(), 0); // obj count
set_ownmem(true);
if (cap > 0 && ptr != m_ptr) {
clear_wkb_data();
// Note: flags_.nbytes doesn't change.
parse_wkb_data(this, get_cptr());
}
}
ptr2 = get_cptr();
ptr = ptr2 + (cap ? cap : sizeof(uint32) /* obj count */);
if (cap == 0) set_nbytes(sizeof(uint32));
} else
has_out_of_line_components(true);
/*
Because the pushed objects have their own memory, here we won't modify
m_ptr memory at all.
*/
for (size_t cnt = sz - ngeo; cnt; cnt--) {
T tmp;
tmp.set_owner(this);
tmp.set_ownmem(false);
// Points are directly put into owner's buffer, no need for own memory.
if (tmp.get_geotype() == Geometry::wkb_point) {
if (is_mpt) {
ptr = write_wkb_header(ptr, Geometry::wkb_point);
set_nbytes(get_nbytes() + WKB_HEADER_SIZE);
}
tmp.set_ptr(ptr, ptsz);
set_nbytes(get_nbytes() + ptsz);
ptr += ptsz;
int4store(get_ucptr(), uint4korr(get_ucptr()) + 1);
DBUG_ASSERT(uint4korr(get_ucptr()) == m_geo_vect->size() + 1);
} else
DBUG_ASSERT(ptr == NULL && ptr2 == NULL);
shallow_push(&tmp);
if (tmp.get_geotype() == Geometry::wkb_polygon)
own_rings(&(m_geo_vect->back()));
// tmp will be filled by push_back after this call, which will make
// tmp own its own memory, different from other geos in m_geo_vect,
// this is OK, users should call reassemble() to put them into
// a single chunk of memory.
}
}
// Explicit template instantiation
/// @cond
template void Gis_wkb_vector<Gis_line_string>::clear();
template void Gis_wkb_vector<Gis_point>::clear();
template void Gis_wkb_vector<Gis_polygon>::clear();
template void Gis_wkb_vector<Gis_polygon_ring>::clear();
template void Gis_wkb_vector<Gis_line_string>::push_back(
Gis_line_string const &);
template void Gis_wkb_vector<Gis_point>::push_back(Gis_point const &);
template void Gis_wkb_vector<Gis_polygon>::push_back(Gis_polygon const &);
template void Gis_wkb_vector<Gis_polygon_ring>::push_back(
Gis_polygon_ring const &);
template void Gis_wkb_vector<Gis_line_string>::reassemble();
template void Gis_wkb_vector<Gis_polygon>::reassemble();
template void Gis_wkb_vector<Gis_line_string>::resize(size_t);
template void Gis_wkb_vector<Gis_point>::resize(size_t);
template void Gis_wkb_vector<Gis_polygon>::resize(size_t);
template void Gis_wkb_vector<Gis_polygon_ring>::resize(size_t);
template Gis_wkb_vector<Gis_line_string>::Gis_wkb_vector(
const void *, size_t, const Geometry::Flags_t &, gis::srid_t, bool);
template Gis_wkb_vector<Gis_polygon>::Gis_wkb_vector(const void *, size_t,
const Geometry::Flags_t &,
gis::srid_t, bool);
template Gis_wkb_vector<Gis_point>::Gis_wkb_vector(const void *, size_t,
const Geometry::Flags_t &,
gis::srid_t, bool);
template Gis_wkb_vector<Gis_point> &Gis_wkb_vector<Gis_point>::operator=(
Gis_wkb_vector<Gis_point> const &);
template Gis_wkb_vector<Gis_point>::Gis_wkb_vector(
Gis_wkb_vector<Gis_point> const &);
template Gis_wkb_vector<Gis_polygon>::Gis_wkb_vector(
const Gis_wkb_vector<Gis_polygon> &);
/// @endcond