536 lines
17 KiB
C++
536 lines
17 KiB
C++
// Copyright (c) 2017, 2018, 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/gis/wkb.h"
|
|
|
|
#include <cmath> // M_PI, M_PI_2
|
|
#include <exception>
|
|
|
|
#include "my_byteorder.h" // float8get, int4store, uint4korr
|
|
#include "my_sys.h" // my_error()
|
|
#include "mysqld_error.h"
|
|
#include "sql/check_stack.h" // check_stack_overrun
|
|
#include "sql/gis/coordinate_range_visitor.h"
|
|
#include "sql/gis/geometries.h"
|
|
#include "sql/gis/geometries_cs.h"
|
|
#include "sql/gis/ring_flip_visitor.h"
|
|
#include "sql/gis/wkb_size_visitor.h"
|
|
#include "sql/gis/wkb_visitor.h"
|
|
#include "sql/sql_const.h" // STACK_MIN_SIZE
|
|
#include "sql/sql_error.h"
|
|
#include "sql/srs_fetcher.h"
|
|
#include "sql_string.h"
|
|
#include "template_utils.h" // pointer_cast
|
|
|
|
namespace gis {
|
|
|
|
/// WKB endianness.
|
|
enum class Byte_order : std::uint8_t {
|
|
/// Big endian
|
|
XDR = 0,
|
|
/// Little endian
|
|
NDR = 1
|
|
};
|
|
|
|
/// Checks if a given type is a valid (and supported) WKB type.
|
|
///
|
|
/// @param type The type to check
|
|
///
|
|
/// @retval true The type is valid.
|
|
/// @retval false The type is invalid.
|
|
static bool is_valid_type(Geometry_type type) {
|
|
switch (type) {
|
|
case Geometry_type::kPoint:
|
|
case Geometry_type::kLinestring:
|
|
case Geometry_type::kPolygon:
|
|
case Geometry_type::kMultipoint:
|
|
case Geometry_type::kMultilinestring:
|
|
case Geometry_type::kMultipolygon:
|
|
case Geometry_type::kGeometrycollection:
|
|
return true;
|
|
default:
|
|
return false; /* purecov: inspected */
|
|
}
|
|
}
|
|
|
|
/// Checks if a given type is a subtype of a given supertype.
|
|
///
|
|
/// @param sub The type to check.
|
|
/// @param super The supertype.
|
|
///
|
|
/// @retval true The type is the supertype or a subtype of it.
|
|
/// @retval false The type is neither the supertype nor a subtype of it.
|
|
static bool is_subtype_of(Geometry_type sub, Geometry_type super) {
|
|
return (super == Geometry_type::kGeometry || sub == super ||
|
|
(super == Geometry_type::kGeometrycollection &&
|
|
(sub == Geometry_type::kMultipoint ||
|
|
sub == Geometry_type::kMultilinestring ||
|
|
sub == Geometry_type::kMultipolygon)));
|
|
}
|
|
|
|
/// Checks if a given type is a valid type and that it is a subtype of a given
|
|
/// supertype.
|
|
///
|
|
/// @param sub The type to check.
|
|
/// @param super The supertype.
|
|
///
|
|
/// @retval true The type is a valid subtype of the supertype.
|
|
/// @retval false The type is invalid or not a subtype of the supertype.
|
|
static bool is_valid_type_or_subtype(Geometry_type sub, Geometry_type super) {
|
|
return is_valid_type(sub) && is_subtype_of(sub, super);
|
|
}
|
|
|
|
template <typename Point_t, typename Linestring_t, typename Linearring_t,
|
|
typename Polygon_t, typename Geometrycollection_t,
|
|
typename Multipoint_t, typename Multilinestring_t,
|
|
typename Multipolygon_t>
|
|
class Wkb_parser {
|
|
private:
|
|
uchar *m_begin;
|
|
uchar *m_end;
|
|
Coordinate_system m_coordinate_system;
|
|
double m_angular_unit;
|
|
double m_prime_meridian;
|
|
bool m_positive_north;
|
|
bool m_positive_east;
|
|
bool m_swap_axes;
|
|
THD *m_thd;
|
|
|
|
double transform_x(double x) {
|
|
DBUG_ASSERT(!std::isnan(x));
|
|
switch (m_coordinate_system) {
|
|
case Coordinate_system::kCartesian:
|
|
// The on-disk and in-memory format is x in SRS direction and unit.
|
|
break;
|
|
case Coordinate_system::kGeographic:
|
|
// The on-disk format is x = longitude, in the SRS direction and unit,
|
|
// and with the SRS meridian.
|
|
// The in-memory format is x = longitude (Easting) in radians with the
|
|
// meridian at Greenwich.
|
|
if (!m_positive_east) x *= -1.0;
|
|
x += m_prime_meridian; // Both are in the SRS angular unit
|
|
x *= m_angular_unit; // Convert to radians
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(false); /* purecov: inspected */
|
|
break;
|
|
}
|
|
|
|
DBUG_ASSERT(!std::isnan(x));
|
|
return x;
|
|
}
|
|
|
|
double transform_y(double y) {
|
|
DBUG_ASSERT(!std::isnan(y));
|
|
switch (m_coordinate_system) {
|
|
case Coordinate_system::kCartesian:
|
|
// The on-disk and in-memory format is y in SRS direction and unit.
|
|
break;
|
|
case Coordinate_system::kGeographic:
|
|
// The on-disk format is y = latitude, in the SRS direction and unit.
|
|
// The in-memory format is y = latitude (Northing) in radians.
|
|
if (!m_positive_north) y *= -1.0;
|
|
y *= m_angular_unit; // Convert to radians
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(false); /* purecov: inspected */
|
|
break;
|
|
}
|
|
|
|
DBUG_ASSERT(!std::isnan(y));
|
|
return y;
|
|
}
|
|
|
|
public:
|
|
Wkb_parser(THD *thd, const dd::Spatial_reference_system *srs,
|
|
bool ignore_axis_order, uchar *begin, uchar *end)
|
|
: m_begin(begin),
|
|
m_end(end),
|
|
m_coordinate_system(Coordinate_system::kCartesian),
|
|
m_angular_unit(1.0),
|
|
m_prime_meridian(0.0),
|
|
m_positive_north(true),
|
|
m_positive_east(true),
|
|
m_swap_axes(false),
|
|
m_thd(thd) {
|
|
if (srs == nullptr || srs->is_cartesian()) {
|
|
m_coordinate_system = Coordinate_system::kCartesian;
|
|
} else if (srs->is_geographic()) {
|
|
m_coordinate_system = Coordinate_system::kGeographic;
|
|
m_angular_unit = srs->angular_unit();
|
|
m_prime_meridian = srs->prime_meridian();
|
|
m_positive_north = srs->positive_north();
|
|
m_positive_east = srs->positive_east();
|
|
if (!ignore_axis_order && srs->is_lat_long()) m_swap_axes = true;
|
|
}
|
|
}
|
|
Byte_order parse_byte_order() {
|
|
if (m_begin + 1 > m_end) throw std::exception();
|
|
|
|
switch (*(m_begin++)) {
|
|
case 0:
|
|
return Byte_order::XDR;
|
|
case 1:
|
|
return Byte_order::NDR;
|
|
}
|
|
|
|
throw std::exception(); /* purecov: inspected */
|
|
}
|
|
|
|
bool reached_end() const { return m_begin == m_end; }
|
|
|
|
std::uint32_t parse_uint32(Byte_order bo) {
|
|
if (m_begin + sizeof(std::uint32_t) > m_end) throw std::exception();
|
|
|
|
std::uint32_t i;
|
|
if (bo == Byte_order::NDR) {
|
|
i = uint4korr(m_begin);
|
|
} else {
|
|
i = load32be(m_begin);
|
|
}
|
|
|
|
m_begin += 4;
|
|
return i;
|
|
}
|
|
|
|
double parse_double(Byte_order bo) {
|
|
if (m_begin + sizeof(double) > m_end) throw std::exception();
|
|
|
|
double d;
|
|
if (bo == Byte_order::NDR) {
|
|
// Little endian data. Use conversion functions to native endianness.
|
|
float8get(&d, m_begin);
|
|
} else {
|
|
#ifdef WORDS_BIGENDIAN
|
|
// Both data and native endianness is big endian. No need to convert.
|
|
memcpy(&d, m_begin, sizeof(double));
|
|
#else
|
|
// Big endian data on little endian CPU. Convert to little endian.
|
|
uchar inv_array[8];
|
|
inv_array[0] = m_begin[7];
|
|
inv_array[1] = m_begin[6];
|
|
inv_array[2] = m_begin[5];
|
|
inv_array[3] = m_begin[4];
|
|
inv_array[4] = m_begin[3];
|
|
inv_array[5] = m_begin[2];
|
|
inv_array[6] = m_begin[1];
|
|
inv_array[7] = m_begin[0];
|
|
float8get(&d, inv_array);
|
|
#endif
|
|
}
|
|
|
|
m_begin += sizeof(double);
|
|
return d;
|
|
}
|
|
|
|
Geometry_type parse_geometry_type(Byte_order bo) {
|
|
if (m_begin + sizeof(std::uint32_t) > m_end) {
|
|
throw std::exception();
|
|
}
|
|
|
|
std::uint32_t wkb_type = parse_uint32(bo);
|
|
Geometry_type type = static_cast<Geometry_type>(wkb_type);
|
|
|
|
if (!is_valid_type_or_subtype(type, Geometry_type::kGeometry))
|
|
throw std::exception();
|
|
return type;
|
|
}
|
|
|
|
Point_t parse_point(Byte_order bo) {
|
|
double x = parse_double(bo);
|
|
double y = parse_double(bo);
|
|
if (!std::isfinite(x) || !std::isfinite(y)) throw std::exception();
|
|
if (m_swap_axes)
|
|
return Point_t(transform_x(y), transform_y(x));
|
|
else
|
|
return Point_t(transform_x(x), transform_y(y));
|
|
}
|
|
|
|
Point_t parse_wkb_point() {
|
|
if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr))
|
|
throw std::exception();
|
|
Byte_order bo = parse_byte_order();
|
|
Geometry_type type = parse_geometry_type(bo);
|
|
if (type != Geometry_type::kPoint) throw std::exception();
|
|
return parse_point(bo);
|
|
}
|
|
|
|
Linestring_t parse_linestring(Byte_order bo) {
|
|
Linestring_t ls;
|
|
std::uint32_t num_points = parse_uint32(bo);
|
|
if (num_points < 2) throw std::exception();
|
|
for (std::uint32_t i = 0; i < num_points; i++) {
|
|
ls.push_back(parse_point(bo));
|
|
}
|
|
return ls;
|
|
}
|
|
|
|
Linestring_t parse_wkb_linestring() {
|
|
if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr))
|
|
throw std::exception();
|
|
Byte_order bo = parse_byte_order();
|
|
Geometry_type type = parse_geometry_type(bo);
|
|
if (type != Geometry_type::kLinestring) throw std::exception();
|
|
return parse_linestring(bo);
|
|
}
|
|
|
|
Polygon_t parse_polygon(Byte_order bo) {
|
|
Polygon_t py;
|
|
std::uint32_t num_rings = parse_uint32(bo);
|
|
if (num_rings == 0) throw std::exception();
|
|
for (std::uint32_t i = 0; i < num_rings; i++) {
|
|
Linearring_t lr;
|
|
std::uint32_t num_points = parse_uint32(bo);
|
|
if (num_points < 4) throw std::exception();
|
|
for (std::uint32_t j = 0; j < num_points; j++) {
|
|
lr.push_back(parse_point(bo));
|
|
}
|
|
py.push_back(std::forward<gis::Linearring>(lr));
|
|
}
|
|
return py;
|
|
}
|
|
|
|
Polygon_t parse_wkb_polygon() {
|
|
if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr))
|
|
throw std::exception();
|
|
Byte_order bo = parse_byte_order();
|
|
Geometry_type type = parse_geometry_type(bo);
|
|
|
|
if (type != Geometry_type::kPolygon) throw std::exception();
|
|
return parse_polygon(bo);
|
|
}
|
|
|
|
Multipoint_t parse_multipoint(Byte_order bo) {
|
|
if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr))
|
|
throw std::exception();
|
|
Multipoint_t mpt;
|
|
std::uint32_t num_points = parse_uint32(bo);
|
|
if (num_points == 0) throw std::exception();
|
|
for (std::uint32_t i = 0; i < num_points; i++) {
|
|
mpt.push_back(parse_wkb_point());
|
|
}
|
|
return mpt;
|
|
}
|
|
|
|
Multilinestring_t parse_multilinestring(Byte_order bo) {
|
|
if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr))
|
|
throw std::exception();
|
|
Multilinestring_t mls;
|
|
std::uint32_t num_linestrings = parse_uint32(bo);
|
|
if (num_linestrings == 0) throw std::exception();
|
|
for (std::uint32_t i = 0; i < num_linestrings; i++) {
|
|
Linestring_t ls;
|
|
mls.push_back(parse_wkb_linestring());
|
|
}
|
|
return mls;
|
|
}
|
|
|
|
Multipolygon_t parse_multipolygon(Byte_order bo) {
|
|
if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr))
|
|
throw std::exception();
|
|
Multipolygon_t mpy;
|
|
std::uint32_t num_polygons = parse_uint32(bo);
|
|
if (num_polygons == 0) throw std::exception();
|
|
for (std::uint32_t i = 0; i < num_polygons; i++) {
|
|
mpy.push_back(parse_wkb_polygon());
|
|
}
|
|
return mpy;
|
|
}
|
|
|
|
Geometrycollection_t parse_geometrycollection(Byte_order bo) {
|
|
if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr))
|
|
throw std::exception();
|
|
Geometrycollection_t gc;
|
|
std::uint32_t num_geometries = parse_uint32(bo);
|
|
for (std::uint32_t i = 0; i < num_geometries; i++) {
|
|
Geometry *g = parse_wkb();
|
|
gc.push_back(std::move(*g));
|
|
delete g;
|
|
}
|
|
return gc;
|
|
}
|
|
|
|
Geometry *parse_wkb() {
|
|
Byte_order bo = parse_byte_order();
|
|
Geometry_type type = parse_geometry_type(bo);
|
|
|
|
switch (type) {
|
|
case Geometry_type::kPoint:
|
|
return new Point_t(parse_point(bo));
|
|
case Geometry_type::kLinestring:
|
|
return new Linestring_t(parse_linestring(bo));
|
|
case Geometry_type::kPolygon:
|
|
return new Polygon_t(parse_polygon(bo));
|
|
case Geometry_type::kMultipoint:
|
|
return new Multipoint_t(parse_multipoint(bo));
|
|
case Geometry_type::kMultilinestring:
|
|
return new Multilinestring_t(parse_multilinestring(bo));
|
|
case Geometry_type::kMultipolygon:
|
|
return new Multipolygon_t(parse_multipolygon(bo));
|
|
case Geometry_type::kGeometrycollection:
|
|
return new Geometrycollection_t(parse_geometrycollection(bo));
|
|
default:
|
|
throw std::exception(); /* purecov: inspected */
|
|
}
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<Geometry> parse_wkb(THD *thd,
|
|
const dd::Spatial_reference_system *srs,
|
|
const char *wkb, std::size_t length,
|
|
bool ignore_axis_order) {
|
|
unsigned char *begin = pointer_cast<unsigned char *>(const_cast<char *>(wkb));
|
|
unsigned char *end = begin + length;
|
|
std::unique_ptr<Geometry> g = nullptr;
|
|
bool res;
|
|
|
|
if (srs == nullptr || srs->is_cartesian()) {
|
|
try {
|
|
Wkb_parser<Cartesian_point, Cartesian_linestring, Cartesian_linearring,
|
|
Cartesian_polygon, Cartesian_geometrycollection,
|
|
Cartesian_multipoint, Cartesian_multilinestring,
|
|
Cartesian_multipolygon>
|
|
parser(thd, srs, ignore_axis_order, begin, end);
|
|
g.reset(parser.parse_wkb());
|
|
res = !g || !parser.reached_end();
|
|
} catch (...) {
|
|
res = true;
|
|
}
|
|
} else if (srs->is_geographic()) {
|
|
try {
|
|
Wkb_parser<Geographic_point, Geographic_linestring, Geographic_linearring,
|
|
Geographic_polygon, Geographic_geometrycollection,
|
|
Geographic_multipoint, Geographic_multilinestring,
|
|
Geographic_multipolygon>
|
|
parser(thd, srs, ignore_axis_order, begin, end);
|
|
g.reset(parser.parse_wkb());
|
|
res = !g || !parser.reached_end();
|
|
} catch (...) {
|
|
res = true;
|
|
}
|
|
} else {
|
|
DBUG_ASSERT(false); /* purecov: inspected */
|
|
return std::unique_ptr<Geometry>();
|
|
}
|
|
|
|
if (res) {
|
|
return std::unique_ptr<Geometry>();
|
|
}
|
|
|
|
return g;
|
|
}
|
|
|
|
bool parse_srid(const char *str, std::size_t length, srid_t *srid) {
|
|
unsigned char *begin = pointer_cast<unsigned char *>(const_cast<char *>(str));
|
|
|
|
if (length < sizeof(srid_t)) return true;
|
|
*srid = uint4korr(begin); // Always little-endian.
|
|
return false;
|
|
}
|
|
|
|
bool parse_geometry(THD *thd, const char *func_name, const String *str,
|
|
const dd::Spatial_reference_system **srs,
|
|
std::unique_ptr<Geometry> *geometry,
|
|
bool treat_unknown_srid_as_cartesian) {
|
|
srid_t srid;
|
|
if (parse_srid(str->ptr(), str->length(), &srid)) {
|
|
my_error(ER_GIS_INVALID_DATA, MYF(0), func_name);
|
|
return true;
|
|
}
|
|
|
|
Srs_fetcher fetcher(thd);
|
|
*srs = nullptr;
|
|
if (srid != 0 && fetcher.acquire(srid, srs)) return true;
|
|
|
|
if (srid != 0 && *srs == nullptr && !treat_unknown_srid_as_cartesian) {
|
|
my_error(ER_SRS_NOT_FOUND, MYF(0), srid);
|
|
return true;
|
|
}
|
|
|
|
*geometry = gis::parse_wkb(thd, *srs, str->ptr() + sizeof(srid_t),
|
|
str->length() - sizeof(srid_t), true);
|
|
if (!(*geometry)) {
|
|
// Parsing failed, assume invalid input data.
|
|
my_error(ER_GIS_INVALID_DATA, MYF(0), func_name);
|
|
return true;
|
|
}
|
|
|
|
// Flip polygon rings so that the exterior ring is counter-clockwise and
|
|
// interior rings are clockwise.
|
|
double semi_major = 1.0;
|
|
double semi_minor = 1.0;
|
|
if (*srs && (*srs)->is_geographic()) {
|
|
semi_major = (*srs)->semi_major_axis();
|
|
semi_minor = (*srs)->semi_minor_axis();
|
|
}
|
|
gis::Ring_flip_visitor rfv(semi_major, semi_minor);
|
|
(*geometry)->accept(&rfv);
|
|
if (rfv.invalid()) {
|
|
// There's something wrong with a polygon in the geometry.
|
|
my_error(ER_GIS_INVALID_DATA, MYF(0), func_name);
|
|
return true;
|
|
}
|
|
|
|
gis::Coordinate_range_visitor crv(*srs);
|
|
if ((*geometry)->accept(&crv)) {
|
|
if (crv.longitude_out_of_range()) {
|
|
my_error(ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE, MYF(0), func_name,
|
|
crv.coordinate_value(), (*srs)->from_radians(-M_PI),
|
|
(*srs)->from_radians(M_PI));
|
|
return true;
|
|
}
|
|
if (crv.latitude_out_of_range()) {
|
|
my_error(ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE, MYF(0), func_name,
|
|
crv.coordinate_value(), (*srs)->from_radians(-M_PI_2),
|
|
(*srs)->from_radians(M_PI_2));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool write_geometry(const dd::Spatial_reference_system *srs, Geometry &geometry,
|
|
String *str) {
|
|
Wkb_size_visitor wkb_size;
|
|
geometry.accept(&wkb_size);
|
|
size_t geometry_size =
|
|
sizeof(std::uint32_t) + wkb_size.size(); // SRID + WKB.
|
|
str->set_charset(&my_charset_bin);
|
|
if (str->reserve(geometry_size)) {
|
|
/* purecov: begin inspected */
|
|
my_error(ER_OUTOFMEMORY, MYF(0));
|
|
return true;
|
|
/* purecov: end */
|
|
}
|
|
str->length(geometry_size);
|
|
char *buffer = &((*str)[0]);
|
|
int4store(buffer, srs == nullptr ? 0 : srs->id());
|
|
buffer += sizeof(std::uint32_t);
|
|
Wkb_visitor wkb(srs, buffer, wkb_size.size());
|
|
geometry.accept(&wkb);
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace gis
|