// Copyright (c) 2017, 2024, Oracle and/or its affiliates. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, // as published by the Free Software Foundation. // // This program is designed to work 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 either included with // the program or referenced in the documentation. // // 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 /// /// This file implements utility functions for working with geometrycollections. #include "sql/gis/gc_utils.h" #include #include // boost::geometry::difference // assert #include "sql/gis/difference_functor.h" #include "sql/gis/geometries.h" #include "sql/gis/geometries_cs.h" #include "sql/gis/geometries_traits.h" #include "sql/gis/union_functor.h" #include "template_utils.h" // down_cast namespace bg = boost::geometry; namespace gis { template static void typed_split_gc(const GC *gc, MPt *mpt, MLs *mls, MPy *mpy) { assert(gc->coordinate_system() == mpt->coordinate_system() && gc->coordinate_system() == mls->coordinate_system() && gc->coordinate_system() == mpy->coordinate_system()); for (const auto g : *gc) { switch (g->type()) { case Geometry_type::kPoint: mpt->push_back(*down_cast(g)); break; case Geometry_type::kLinestring: mls->push_back(*down_cast(g)); break; case Geometry_type::kPolygon: mpy->push_back(*down_cast(g)); break; case Geometry_type::kGeometrycollection: typed_split_gc(down_cast(g), mpt, mls, mpy); break; case Geometry_type::kMultipoint: { const MPt *m = down_cast(g); for (std::size_t i = 0; i < m->size(); i++) mpt->push_back(static_cast((*m)[i])); break; } case Geometry_type::kMultilinestring: { const MLs *m = down_cast(g); for (std::size_t i = 0; i < m->size(); i++) mls->push_back(static_cast((*m)[i])); break; } case Geometry_type::kMultipolygon: { const MPy *m = down_cast(g); for (std::size_t i = 0; i < m->size(); i++) mpy->push_back(static_cast((*m)[i])); break; } case Geometry_type::kGeometry: assert(false); break; } } } void split_gc(const Geometrycollection *gc, std::unique_ptr *mpt, std::unique_ptr *mls, std::unique_ptr *mpy) { switch (gc->coordinate_system()) { case Coordinate_system::kCartesian: mpt->reset(new Cartesian_multipoint()); mls->reset(new Cartesian_multilinestring()); mpy->reset(new Cartesian_multipolygon()); typed_split_gc( down_cast(gc), down_cast(mpt->get()), down_cast(mls->get()), down_cast(mpy->get())); break; case Coordinate_system::kGeographic: mpt->reset(new Geographic_multipoint()); mls->reset(new Geographic_multilinestring()); mpy->reset(new Geographic_multipolygon()); typed_split_gc( down_cast(gc), down_cast(mpt->get()), down_cast(mls->get()), down_cast(mpy->get())); break; } } template void typed_gc_union(double semi_major, double semi_minor, std::unique_ptr *mpt, std::unique_ptr *mls, std::unique_ptr *mpy) { Difference difference(semi_major, semi_minor); Union union_(semi_major, semi_minor); std::unique_ptr polygons(new MPy()); for (auto &py : *down_cast(mpy->get())) { std::unique_ptr union_result = union_(polygons.get(), &py); if (union_result->type() == Geometry_type::kPolygon) { polygons->clear(); polygons->push_back(*union_result); } else if (union_result->type() == Geometry_type::kMultipolygon) { polygons.reset(new MPy(*down_cast(union_result.get()))); } if (polygons->coordinate_system() == Coordinate_system::kGeographic && polygons->is_empty()) { // The result of a union between a geographic multipolygon and a // geographic polygon is empty. There are two reasons why this may happen: // // 1. One of the polygons involved are invalid. // 2. One of the polygons involved covers half the globe, or more. // // Since invalid input is only reported to the extent it is explicitly // detected, we can simply return a too large polygon error in both cases. throw too_large_polygon_exception(); } } std::unique_ptr linestrings = std::make_unique(); std::unique_ptr ls_difference( difference(mls->get(), polygons.get())); if (ls_difference->type() == Geometry_type::kLinestring) linestrings->push_back(*ls_difference); else linestrings.reset(down_cast(ls_difference.release())); std::unique_ptr points = std::make_unique(); std::unique_ptr pt_difference( difference(mpt->get(), linestrings.get())); pt_difference = difference(pt_difference.get(), polygons.get()); if (pt_difference->type() == Geometry_type::kPoint) { points->push_back(*pt_difference); } else points.reset(down_cast(pt_difference.release())); mpy->reset(polygons.release()); mls->reset(linestrings.release()); mpt->reset(points.release()); } void gc_union(double semi_major, double semi_minor, std::unique_ptr *mpt, std::unique_ptr *mls, std::unique_ptr *mpy) { assert(mpt->get() && mls->get() && mpy->get()); assert((*mpt)->coordinate_system() == (*mls)->coordinate_system() && (*mpt)->coordinate_system() == (*mpy)->coordinate_system()); // We're using empty GCs to detect invalid geometries, so empty geometry // collections should be filtered out before calling gc_union. assert(!(*mpt)->empty() || !(*mls)->empty() || !(*mpy)->empty()); switch ((*mpt)->coordinate_system()) { case Coordinate_system::kCartesian: { typed_gc_union(semi_major, semi_minor, mpt, mls, mpy); break; } case Coordinate_system::kGeographic: { typed_gc_union(semi_major, semi_minor, mpt, mls, mpy); break; } } // If all collections are empty, we've encountered at least one invalid // geometry. if ((*mpt)->empty() && (*mls)->empty() && (*mpy)->empty()) throw invalid_geometry_exception(); assert(mpt->get() && mls->get() && mpy->get()); assert(!(*mpt)->empty() || !(*mls)->empty() || !(*mpy)->empty()); } std::unique_ptr narrowest_multigeometry( std::unique_ptr geometrycollection) { bool pt = false; bool ls = false; bool py = false; for (size_t i = 0; i < geometrycollection->size(); i++) { switch (geometrycollection->operator[](i).type()) { case gis::Geometry_type::kPoint: if (ls || py) return geometrycollection; pt = true; break; case gis::Geometry_type::kLinestring: if (pt || py) return geometrycollection; ls = true; break; case gis::Geometry_type::kPolygon: if (pt || ls) return geometrycollection; py = true; break; case gis::Geometry_type::kMultipoint: case gis::Geometry_type::kMultilinestring: case gis::Geometry_type::kMultipolygon: case gis::Geometry_type::kGeometrycollection: return geometrycollection; default: break; } } // Otherwise create the necessary multigeometries and split the input. std::unique_ptr multipoint = std::unique_ptr(gis::Multipoint::create_multipoint( geometrycollection->coordinate_system())); std::unique_ptr multilinestring = std::unique_ptr( gis::Multilinestring::create_multilinestring( geometrycollection->coordinate_system())); std::unique_ptr multipolygon = std::unique_ptr(gis::Multipolygon::create_multipolygon( geometrycollection->coordinate_system())); gis::split_gc(geometrycollection.get(), &multipoint, &multilinestring, &multipolygon); if (!multipoint->empty()) return std::unique_ptr(multipoint.release()); else if (!multilinestring->empty()) return std::unique_ptr(multilinestring.release()); else if (!multipolygon->empty()) return std::unique_ptr(multipolygon.release()); assert(false); return geometrycollection; } } // namespace gis