174 lines
6.6 KiB
C++
174 lines
6.6 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.
|
|
|
|
/// @file
|
|
///
|
|
/// Implements the distance_sphere functor and function.
|
|
|
|
#include "sql/gis/distance_sphere.h"
|
|
#include "sql/gis/distance_sphere_functor.h"
|
|
|
|
#include <boost/geometry.hpp>
|
|
#include <cmath> // std::isinf, M_PI
|
|
#include <stdexcept> // std::overflow_error
|
|
|
|
#include "my_dbug.h" // DBUG_ASSERT
|
|
#include "sql/dd/types/spatial_reference_system.h" // dd::Spatial_reference_system
|
|
#include "sql/gis/functor.h" // gis::Functor, gis::not_implemented_exception
|
|
#include "sql/gis/geometries.h" // gis::{Geometry{,_type}, Coordinate_system}
|
|
#include "sql/gis/geometries_cs.h" // gis::{Cartesian_*, Geographic_*}
|
|
#include "sql/gis/geometries_traits.h" // boost::geometry traits for gis types
|
|
#include "sql/sql_exception_handler.h" // handle_gis_exception
|
|
|
|
namespace bg = boost::geometry;
|
|
|
|
namespace gis {
|
|
|
|
/// Map Cartesian geometry to geographic, mapping degrees east = x, degrees
|
|
/// north = y. Do not canonicalize coordinates of poles.
|
|
///
|
|
/// Used when a SQL function needs to accept Cartesian coordiates as a shorthand
|
|
/// for geographic with some default SRS.
|
|
static Geographic_point reinterpret_as_degrees(const Cartesian_point &g) {
|
|
double lon_deg = g.x();
|
|
double lat_deg = g.y();
|
|
|
|
if (!(-180.0 < lon_deg && lon_deg <= 180.0))
|
|
throw longitude_out_of_range_exception(lon_deg, -180.0, 180.0);
|
|
|
|
if (!(-90.0 <= lat_deg && lat_deg <= 90.0))
|
|
throw latitude_out_of_range_exception(lat_deg, -90.0, 90.0);
|
|
|
|
return {lon_deg * M_PI / 180.0, lat_deg * M_PI / 180.0};
|
|
}
|
|
|
|
/// Map Cartesian geometry to geographic, mapping degrees east = x, degrees
|
|
/// north = y. Do not canonicalize coordinates of poles.
|
|
///
|
|
/// Used when a SQL function needs to accept Cartesian coordiates as a shorthand
|
|
/// for geographic with some default SRS.
|
|
static Geographic_multipoint reinterpret_as_degrees(
|
|
const Cartesian_multipoint &g) {
|
|
Geographic_multipoint dg{};
|
|
for (auto const &point : g) {
|
|
dg.push_back(reinterpret_as_degrees(point));
|
|
}
|
|
return dg;
|
|
}
|
|
|
|
double Distance_sphere::operator()(const Geometry *g1,
|
|
const Geometry *g2) const {
|
|
return apply(*this, g1, g2);
|
|
}
|
|
|
|
double Distance_sphere::eval(const Cartesian_point *g1,
|
|
const Cartesian_point *g2) const {
|
|
// The parser interprets SRID 0 coordinates as Cartesian. This is incorrect
|
|
// for distance_sphere that takes spherical coordinates in degrees.
|
|
// Convert to internal representation for geographic coordinates.
|
|
Geographic_point rg1 = reinterpret_as_degrees(*g1);
|
|
Geographic_point rg2 = reinterpret_as_degrees(*g2);
|
|
return eval(&rg1, &rg2);
|
|
}
|
|
|
|
double Distance_sphere::eval(const Cartesian_point *g1,
|
|
const Cartesian_multipoint *g2) const {
|
|
// Distance is commutative.
|
|
return eval(g2, g1);
|
|
}
|
|
|
|
double Distance_sphere::eval(const Cartesian_multipoint *g1,
|
|
const Cartesian_point *g2) const {
|
|
Geographic_multipoint rg1 = reinterpret_as_degrees(*g1);
|
|
Geographic_point rg2 = reinterpret_as_degrees(*g2);
|
|
return eval(&rg1, &rg2);
|
|
}
|
|
|
|
double Distance_sphere::eval(const Cartesian_multipoint *g1,
|
|
const Cartesian_multipoint *g2) const {
|
|
Geographic_multipoint rg1 = reinterpret_as_degrees(*g1);
|
|
Geographic_multipoint rg2 = reinterpret_as_degrees(*g2);
|
|
return eval(&rg1, &rg2);
|
|
}
|
|
|
|
double Distance_sphere::eval(const Geographic_point *g1,
|
|
const Geographic_point *g2) const {
|
|
return bg::distance(*g1, *g2, m_strategy);
|
|
}
|
|
|
|
double Distance_sphere::eval(const Geographic_point *g1,
|
|
const Geographic_multipoint *g2) const {
|
|
return bg::distance(*g1, *g2, m_strategy);
|
|
}
|
|
|
|
double Distance_sphere::eval(const Geographic_multipoint *g1,
|
|
const Geographic_point *g2) const {
|
|
return bg::distance(*g1, *g2, m_strategy);
|
|
}
|
|
|
|
double Distance_sphere::eval(const Geographic_multipoint *g1,
|
|
const Geographic_multipoint *g2) const {
|
|
// Boost does not yet implement distance between two multipoints. Find
|
|
// minumum by iterating over multipoint-point distances.
|
|
double minimum = eval(g1, &(*g2)[0]);
|
|
for (size_t i = 1; i < g2->size(); i++) {
|
|
double d = eval(g1, &(*g2)[i]);
|
|
if (d < minimum) minimum = d;
|
|
}
|
|
return minimum;
|
|
}
|
|
|
|
double Distance_sphere::eval(const Geometry *g1, const Geometry *g2) const {
|
|
throw not_implemented_exception::for_non_projected(*g1, *g2);
|
|
}
|
|
|
|
bool distance_sphere(const dd::Spatial_reference_system *srs,
|
|
const Geometry *g1, const Geometry *g2,
|
|
const char *func_name, double sphere_radius,
|
|
double *result, bool *result_null) noexcept {
|
|
try {
|
|
DBUG_ASSERT(g1->coordinate_system() == g2->coordinate_system());
|
|
DBUG_ASSERT(!srs || srs->is_cartesian() || srs->is_geographic());
|
|
DBUG_ASSERT(!srs || srs->is_cartesian() == (g1->coordinate_system() ==
|
|
Coordinate_system::kCartesian));
|
|
DBUG_ASSERT(!srs ||
|
|
srs->is_geographic() == (g1->coordinate_system() ==
|
|
Coordinate_system::kGeographic));
|
|
|
|
*result_null = false;
|
|
|
|
if (srs && srs->is_projected())
|
|
throw not_implemented_exception::for_projected(*g1, *g2);
|
|
|
|
*result = Distance_sphere{sphere_radius}(g1, g2);
|
|
|
|
if (std::isinf(*result)) throw std::overflow_error("INFINITY");
|
|
|
|
return false;
|
|
} catch (...) {
|
|
handle_gis_exception(func_name);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
} // namespace gis
|